ข้ามไปยังเนื้อหา

พื้นฐานการเขียนโปรแกรม

วิทยาการคอมพิวเตอร์ มหาวิทยาลัย · หัวข้อ 1

ดูสไลด์ ฝึกฝน
1.1

หลักสูตรนี้คืออะไร และการใช้หลักสูตรนี้

นี่คือรายวิชาแรกในสาขาวิทยาการคอมพิวเตอร์ของมหาวิทยาลัย: พื้นฐานการเขียนโปรแกรม , สอนด้วย Python 3. โดยไม่จำเป็นต้องมีความรู้ด้านการเขียนโปรแกรมมาก่อน ทุกโปรแกรมใน this handout is complete and runnable — type it, run it, change one line, and predict ว่าจะเกิดอะไรขึ้นก่อนรันอีกครั้ง ลูป predict-then-run นี้คือทักษะทั้งหมด

รายวิชานี้เป็นบันได:

  • หน่วย 1.1 ถึง 1.3 สร้างพื้นฐานด้านกลไก: ค่าและประเภท, การเลือกเงื่อนไข, การวนซ้ำ
  • หน่วย 1.4 จัดการงานให้อยู่ในรูปฟังก์ชัน
  • หน่วย 1.5 และ 1.6 เพิ่มคอลเลกชันมาตรฐานและไฟล์พร้อมการจัดการข้อผิดพลาด
  • หน่วย 1.7 เป็นทักษะชั้นบน: การแยกปัญหาออกเป็นส่วนย่อย, การทดสอบ, การแก้ไขโค้ด

วิธีการอ่านแต่ละหน่วย: เรื่องสั้นสั้นๆ บอกเหตุผลว่าทำไมมันจึงสำคัญ; ภาพหรือกฎเชิงปฏิบัติ มาก่อนไวยากรณ์; แล้วคือกฎที่ชัดเจน; จากนั้นตัวอย่างวิธีทำที่ซับซ้อนขึ้น, แต่ละตัวอย่างมีเทรซที่คุณสามารถตรวจสอบได้โดยการรันโค้ด; จากนั้นคือความผิดพลาดที่คนจริงมัก กระทำ; แล้วสรุปสามบรรทัด สรุปสั้นๆ ทุกตัวอย่างในที่นี้สามารถตรวจสอบได้อีกวิธีหนึ่ง — ด้วยการรันมันเอง การอ่านโค้ดโดยไม่รันมันก็เหมือนการอ่านโน้ตดนตรีโดยไม่ ได้ยินเสียง

คำศัพท์ ฝึกฝน
English ไทย
programming fundamentals/ˈprəʊɡræmɪŋ ˌfʌndəˈmentlz/ พื้นฐานการเขียนโปรแกรม
type/taɪp/ ประเภท
1.1

ค่า ประเภท และการรับ/ส่งข้อมูลเข้า-ออก

แอปช้อปปิ้งแสดง "4.20" — นั่นคือข้อความหรือตัวเลข? ถ้าเป็นข้อความ การบวกสอง ราคาจะได้ "4.203.50"; ถ้าเป็นตัวเลข จะได้ 8.7. สิ่งที่จะเกิดขึ้นขึ้นอยู่กับ โดย ประเภท และเนื้อหาทั้งหมดของหน่วย 1.1 คือการเรียนรู้ที่จะมองเห็นประเภทก่อนจะ รันอะไรเลย

ค่า มีประเภท, และประเภทกำหนดสิ่งที่คุณทำได้กับมัน: int (จำนวนเต็ม จำนวน), float (ทศนิยม), str (ข้อความ), bool (True/False). ฟังก์ชัน type(...) ระบุประเภทของค่าใดๆ — สามารถเรียกใช้ได้อย่างอิสระขณะเรียนรู้

age = 21              # int
price = 4.20          # float
name = "Ada"          # str
open_ = True          # bool
print(type(age))      # <class 'int'>

ตัวแปร คือชื่อที่ผูกกับค่า; การกำหนดค่า age = 22 จะผูกชื่อใหม่ — it never edits the old value. The mental picture, worth keeping for the whole degree:

สถานะโปรแกรมในรูปกล่อง: แต่ละป้ายชื่อชี้ไปที่กล่องค่าของตนเอง; การผูกใหม่ย้ายป้าย แต่ไม่แก้ไขกล่อง ค่าแสดง 6 * 3.50 อ่านสองกล่องและสร้างกล่อง float ใหม่ 21.0
ชื่อเป็นป้ายบนกล่อง; การอ่านนิพจน์จะเปิดกล่องและสร้างกล่องใหม่

การอ่านนิพจน์คือ "เปิดกล่อง คำนวณ ปิดกล่องใหม่": count * 3.50 with count = 6 opens the int box 6 and the float box 3.50, and Python's mixing กฎจะยกระดับ int — ผลลัพธ์จะเป็น float 21.0 ไม่ใช่ int 21.

การแปลงชนิดต้องระบุชัดเจน: int("21") คือ 21 แต่ "4" + 2 เป็น TypeError — Python ปฏิเสธที่จะเดาว่าคุณหมายถึง "42" หรือ 6. ใช้ output กับ print(...); ใช้ input กับ input(...), ซึ่งให้ผลลัพธ์เป็น string เสมอ ดังนั้นตัวเลขจึงต้องแปลง:

born = int(input("Birth year? "))
print("About", 2026 - born, "years old")

ตัวอย่างทำแล้ว 1 (เครื่องคิดเงินคาเฟ่). count = int(input("Coffees? ")), แล้ว print("Total:", count * 3.50). ที่ 6 แก้วกาแฟ โปรแกรมจะพิมพ์ Total: 21.0 — ซึ่งเป็น .0 ทำเครื่องหมายเป็น float เพราะ int * float ถูกแปลงเป็น float การสังเกตว่าผลลัพธ์มี of an expression is unit 1.1's whole habit. Trace it the state way: input returns the string "6"; int(...) opens a new box holding the int 6; the multiplication makes a fresh float box 21.0; print shows it.

Worked example 2 (the classic accident, repaired). A banner printer joins a count onto a message:

count = int(input("Tickets? "))
print("Sold " + count + " today")

รันโค้ดแล้ว Python จะแสดง TypeError: can only concatenate str (not "int") to str — กฎนี้ปฏิเสธการเดา การแก้ไขจะระบุการแปลงชนิดข้อมูลอย่างชัดเจน: print("Sold " + str(count) + " today") หรือ print("Sold", count, "today"), which inserts the spaces for you. The lesson to carry: + หมายความว่าบวกเข้าด้วยกัน สำหรับตัวเลข ให้ใช้การเชื่อมต่อแบบ string และหลีกเลี่ยงการผสมที่ไร้เสียง

ข้อผิดพลาดที่พบบ่อย การลืมว่า input() จะให้ผลลัพธ์เป็น string เสมอ ดังนั้น input() * 2 จึงเป็นการซ้ำข้อความแทนที่จะคูณสองจำนวน เขียน 4.20 + 6 และ คาดหวังผลลัพธ์ int — หากมีค่า float ใดๆ ในนิพจน์ ผลลัพธ์จะเป็น float ทั้งหมด ความเชื่อผิดๆ Regarding 0.1 + 0.2 == 0.3 (มันคือ False; การปัดเศษแบบไบนารี — หน่วย 1.2 แสดงวิธีแก้ไข)

สรุป: ประเภทพื้นฐานสี่ชนิดกำหนดการดำเนินการทั้งหมด; ชื่อเป็นแท็ก, การมอบหมายใหม่จะเปลี่ยนการเชื่อมโยง; นิพจน์จะผสมประเภทด้วยการยกระดับด้วย float; การแปลงต้องระบุชัดเจนและ input() ต้องการหนึ่งอย่างเท่านั้น

คำศัพท์ ฝึกฝน
English ไทย
value/ˈvæljuː/ ค่า
variables/ˈveərɪəblz/ ตัวแปร
string/strɪŋ/ สตริง
selection/sɪˈlekʃn/ การเลือก
iteration/ˌɪtəˈreɪʃn/ การวนซ้ำ
infinite loop/ˈɪnfɪnət luːp/ ลูปไม่สิ้นสุด
set/set/ เซต
1.2

การเลือก: การเลือกเส้นทาง

ประตูหมุนสวนสนุกต้องคิดค่าเข้าเด็ก ผู้ใหญ่ และผู้สูงอายุต่างกัน — คนอื่น การเลือก คือวิธีที่โปรแกรมตัดสินใจ: if, elif, อายุ else, นักเรียน: สาขาที่จริงก่อนสุดจะกำหนดราคา

แผนภาพการไหลของกฎราคาโรงภาพยนตร์พร้อมการตรวจสอบอายุก่อนการตรวจสอบสถานะนักศึกษา; นักศึกษาอายุ 70 ปีจะเลือกเส้นทางที่จริงเป็นอันดับแรกและชำระ 5
อายุ 70, นักเรียน: สาขาที่จริงเป็นครั้งแรกจะกำหนดราคา

เงื่อนไขคือนิพจน์แบบboolean ใดๆ การเปรียบเทียบ (==, !=, <, >=) และ logic (and, or, not) build it. Two rules make every decision readable:

  1. สาขาจะถูกทดสอบจากบนลงล่าง; สาขาที่จริงเป็นครั้งแรกจะชนะ ส่วนที่เหลือจะ ถูกข้ามไป
  2. Indentation is not decoration — it IS the block. One extra space changes which บรรทัดใดจัดอยู่ในสาขานั้น
temp = 37.5
if temp >= 38:
    print("Fever")
elif temp >= 36 and temp < 38:
    print("Normal")
else:
    print("Low")

ตัวอย่างคำอธิบาย 1 (ราคาโรงภาพยนตร์) ภายใต้ 12 หรือ 65+ จ่าย 5, นักเรียนจ่าย 7, ทุกคนOthers 10:

age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
    price = 5
elif student:
    price = 7
else:
    price = 10
print("Pay", price)

ลำดับมีความสำคัญ: สาขาที่จริงเป็นอันดับแรกจะชนะ นี่คือเหตุผลว่าทำไมการตรวจสอบอายุจึงมาก่อนการ ตรวจสอบนักเรียน — นักเรียนอายุ 70 ปี ควรจ่าย 5 และการจัดลำดับนี้ชี้แจงเรื่องดังกล่าว รัน ด้วยค่า 70 และ y เพื่อดูเส้นทางด้านซ้ายสุดของแผนผังการไหลที่ถูกเลือก

ตัวอย่างวิธีทำ 2 (ช่วงเกรด, แบบเพิ่มระดับ) โครงสร้างเดิม แต่ตอนนี้มีสายโซ่ ที่แต่ละ elif ต้องการการเปรียบเทียบเพียงครั้งเดียว เพราะการทดสอบที่อยู่ด้านบนได้ล้มเหลวไปก่อนแล้ว: ล้มเหลว:

score = int(input("Score? "))
if score >= 80:
    band = "A"
elif score >= 70:
    band = "B"
elif score >= 60:
    band = "C"
else:
    band = "F"
print("Band:", band)

ข้อสังเกต: ค่า 79 ไม่จำเป็นต้องใช้ Band: B อีกต่อไป — การเข้าถึงบรรทัดนั้นพิสูจน์แล้วว่าคะแนนอยู่ต่ำกว่า สังเกตว่า: elif score >= 70 ไม่จำเป็นต้องใช้ and score < 80 ด้วย — การเข้าถึงบรรทัดนั้น line already proves the score is below 80. Trusting the fall-through like this is สิ่งที่ทำให้โซ่สั้นลง: การทดสอบซ้ำทุกสิ่งเป็นสิ่งที่ผู้เริ่มต้นทำ ซึ่งยาวนานกว่าและผิดกว่า version.

การเปรียบเทียบค่าทศนิยมต้องระวัง: 0.1 + 0.2 == 0.3 เป็น False (การปัดเศษแบบฐานสอง) ดังนั้นให้เปรียบเทียบกับค่าความคลาดเคลื่อน: abs(x - y) < 1e-9

ข้อผิดพลาดที่พบบ่อย การเขียน = แทนที่จะเป็น == — ซึ่งเป็นบักคลาสสิก และ Python ปฏิเสธการใช้ในเงื่อนไข (SyntaxError) โดยตรงเพราะเป็นเรื่องที่พบได้บ่อยมาก การทดสอบ กรณีพิเศษที่เกิดขึ้นหลังจากกรณีทั่วไป ดังนั้นกรณีทั่วไปจึงชนะเสมอ การใช้ elif กับเงื่อนไขที่ไม่สามารถเกิดขึ้นได้เลยเนื่องจากสาขาก่อนหน้าจับไว้แล้ว จับมันไว้ได้

สรุป: เงื่อนไขเลือกเส้นทาง, จริงเป็นอันดับแรกชนะ, การขึ้นต้นบรรทัดคือบล็อก; ลำดับ การทดสอบจากเฉพาะเจาะจงไปทั่วไป; การเปรียบเทียบค่าลอยใช้ช่วงความคลาดเคลื่อน ไม่ใช้ ==

1.3

การทำซ้ำ: ทำอีกครั้ง

การชำระเงิน 5,000 ด้วยมือเป็นอาชีพ แต่เมื่อใช้ลูปจะเป็นโปรแกรม การทำซ้ำ ทำซ้ำงาน: ลูป for จะวนผ่านลำดับที่ทราบล่วงหน้า; ลูป while จะทำงานจนกว่าจะมี เงื่อนไขเป็นจริง คำถามในการออกแบบคือเสมอว่า: คุณรู้จำนวนรอบหรือไม่มี, หรือมี a condition decide?

total = 0
for i in range(1, 101):      # 1..100 — range stops BEFORE its end
    total += i
print(total)                  # 5050

Use for when the trip count is known; use while when a condition decides. Every while ต้องการสามสิ่งที่เห็นได้ในโค้ด: จุดเริ่มต้นของตัวแปร, เงื่อนไข, และสิ่งภายในที่ทำให้เข้าใกล้การออก — มิฉะนั้นจะเป็น ลูปไม่สิ้นสุด ลูป

ลูป while ค่าน factorial อยู่ข้างๆ ตารางติดตามผล: ค่า n และ factorial หลังแต่ละรอบ ตั้งแต่ 5 และ 1 ไปจนถึง 1 และ 120 เมื่อเงื่อนไขกลายเป็น False
ตารางติดตามสำหรับลูปแฟกทอเรียล
n = 5
factorial = 1
while n > 1:
    factorial *= n
    n -= 1                    # the step toward exiting
print(factorial)              # 120

ตารางติดตาม เป็นนิสัยการตรวจสอบของรายวิชาสำหรับลูป: ติดตามตัวแปร รอบต่อรอบ บนกระดาษก่อน สำหรับลูปแฟกทอเรียล: (n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). สร้างตารางด้วยมือ แล้ว เพิ่ม print(n, factorial) ภายในลูป และให้ Python ตรวจคะแนนให้คุณ

ตัวอย่างที่คำนวณแล้ว 2 (การค้นหาด้วย break, ยกระดับ) หาเลขคูณของ 7 that is greater than 100 — the trip count is unknown, the condition decides:

n = 100
while True:
    if n % 7 == 0:
        break                 # found it — leave the loop now
    n += 1
print(n)                      # 105

การติดตามสั้น: 100, 101, …, 104 ล้มเหลวในการทดสอบทั้งหมด; 105 ผ่านและ break ออก. break ออกจากลูปก่อนกำหนด; continue ข้ามไปยังรอบถัดไป ทั้งสองอย่างนี้เป็น readability tools — a loop that must scan until it finds something, then stop, reads ดีที่สุดใน break. การเพิ่มยอดเงินสองเท่า: ที่ 5% ต่อปี ใช้เวลานานเท่าใด 1000 จะใช้เวลาถึง 2000?

balance = 1000.0
years = 0
while balance < 2000.0:       # the condition itself is the goal
    balance *= 1.05
    years += 1
print(years, round(balance, 2))   # 15 2078.93

สิบห้าปี และลูปจะหยุดทันทีที่เงื่อนไขเปลี่ยน — ตารางติดตาม (trace table) จะแสดงว่ายอดเงินข้ามเส้นที่ 2078.93 ไม่ใช่ตกที่ 2000 พอดี

ข้อผิดพลาดที่พบบ่อย การแก้ไขรายการ (หน่วย 1.5) หรือตัวแปรลูปที่คุณกำลัง for-วนซ้ำ range(1, 100) เมื่อควรใช้ 100 — จุดสิ้นสุดไม่รวมอยู่ด้วย while ที่มีขั้นตอนออกขาดหายหรือถูก Comment ทิ้ง: เงื่อนไขไม่เคยเปลี่ยนไป, และโปรแกรมจะค้าง

สรุป: for สำหรับจำนวนรอบที่ทราบล่วงหน้า, while สำหรับเงื่อนไข; ลูปทุกชนิดมีจุดเริ่มต้น, เงื่อนไขและขั้นตอน; ทดสอบบนกระดาษ แล้วพิมพ์ระดับเกรด; break และ continue สำหรับ การออกจากลูปอย่างสะอาดและก่อนกำหนด

คำศัพท์ ฝึกฝน
English ไทย
trace table/treɪs ˈteɪbl/ ตารางติดตาม
1.4

ฟังก์ชัน: ตั้งชื่องานครั้งเดียว

ห้าจุดถูกอัปเดต ฟังก์ชัน จัดแพ็กเกจงานไว้หลังชื่อเพื่อให้เขียน five get updated. A function packages a job behind a name so it is written กำหนด; การเรียกใช้งานดำเนินการ def กำหนด; การเรียกใช้งานดำเนินการ defines; calling runs.

def total_price(count, unit=3.50):
    """Return the total for count items at unit price."""
    return count * unit

print(total_price(6))            # 21.0
print(total_price(6, unit=4.0))  # 24.0

The definition gives a parameter (count, unit); the call supplies an argument (6). A default value (unit=3.50) makes an argument optional. return hands a value back and ends the call; printing is not returning — a function without return returns None, and the caller that tries arithmetic on it ได้รับ TypeError

กรอบการเรียกใช้สามครั้ง: print เรียก order_total(250, 4) ซึ่งหยุดชั่วคราวเมื่อ add_fee(1000) ทำงาน; 1003 ถูกส่งกลับตามลำดับการเรียกใช้
การเรียกใช้แบบซ้อน: ผู้เรียกจะหยุดจนกว่าค่าจะถูกส่งกลับ

แต่ละการเรียกใช้จะมี พื้นที่ทำงานเฉพาะตัว: ตัวแปรภายในจะมองไม่เห็น ภายนอก และจะหายไปเมื่อการเรียกสิ้นสุด การติดตามการเรียกใช้แบบซ้อนอ่านได้ดังนี้: ผู้เรียกหยุด → ผู้ถูกเรียกทำงาน → ค่าที่ส่งกลับเข้าสู่ผู้เรียก

ตัวอย่างคำอธิบาย 1 (คำสั่งรวมที่รวมค่าธรรมเนียมแล้ว).

def add_fee(amount):
    """Return amount plus a fixed 3 pence fee."""
    return amount + 3

def order_total(unit_pence, quantity):
    """Return the order total: subtotal plus the fixed fee."""
    return add_fee(unit_pence * quantity)

print(order_total(250, 4))   # 1003

สัญญาในเอกสารประกอบมีความสำคัญ: เข้ามาเต็มหน่วย ออกมาเต็มหน่วย การติดตาม order_total(250, 4): ส่วนย่อย 1000, add_fee ส่งกลับ 1003 — และกรอบ แผนภาพด้านบนแสดงการหยุดและส่งคืน

ตัวอย่างคำอธิบาย 2 (อัลกอริทึมจริงภายในฟังก์ชันเล็ก ๆ ที่ขยาย). is_prime(n) ส่งกลับ True เมื่อไม่มีอะไรตั้งแต่ 2 ถึง $\sqrt{n}$ หาร n ลงตัว:

def is_prime(n):
    if n < 2:
        return False
    d = 2
    while d * d <= n:        # only test up to the square root
        if n % d == 0:
            return False     # found a divisor — answer known, stop
        d += 1
    return True

print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

พฤติกรรมสองอย่างของฟังก์ชันมาพบกันที่นี่: การส่งกลับ return ล่วงหน้าจะจบการเรียกทันทีที่ทราบคำตอบ เป็นสิ่งที่ทราบได้ล่วงหน้า (ไม่จำเป็นต้องใช้ตัวแปร flag) และขอบเขตของลูป d * d <= n มาจากการ การหาอนุพันธ์ — หาก $n = a \times b$ พร้อมกับ $a \le b$ แล้ว จะได้ $a \le \sqrt{n}$ ดังนั้นการทดสอบเกิน $\sqrt{n}$ จะทดสอบตัวหารที่เคยเห็นมาแล้วใหม่ รันโค้ดนี้: รายการที่ได้จะเป็นจำนวนเฉพาะที่น้อยกว่า 30 พอดี

การเข้าถึงตัวแปรภายในของฟังก์ชันจากภายนอก; ตัวแปรเหล่านี้จะหายไปเมื่อการเรียกสิ้นสุด ไม่สามารถนำมาใช้ซ้ำได้ การลืม return ในเส้นทางหนึ่ง — เส้นทางนั้นจะให้ผลลัพธ์เป็น None โดยอัตโนมัติ การเข้าถึงตัวแปรภายในฟังก์ชันจากภายนอก—they disappears เมื่อการเรียกใช้ สิ้นสุดลง

สรุป: def ครั้ง, เรียกได้ทุกที่; พารามิเตอร์เข้า, ค่าออกหนึ่งค่าผ่าน return; การกลับคืนก่อนกำหนดจะสิ้นสุดการเรียกใช้; ตัวแปรท้องถิ่นมีอยู่และหายไปพร้อมกับการเรียกใช้; docstring บอกล่วงหน้าถึง สัญญาของฟังก์ชัน

คำศัพท์ ฝึกฝน
English ไทย
function/ˈfʌŋkʃn/ ฟังก์ชัน
return value/rɪˈtɜːn ˈvæljuː/ ค่าที่ส่งกลับ
parameter/pəˈræmɪtə/ พารามิเตอร์
argument/ˈɑːɡjuːmənt/ อาร์กิวเมนต์
local scope/ˈləʊkl skəʊp/ สเปกตรัมท้องถิ่น
1.5

โครงสร้างข้อมูล: ลิสต์, ไดร์กชันนารี, ทูเปิล, เซ็ต

เครื่องรับเงินร้านกาแฟต้องการคำสั่งซื้อประจำวันตามลำดับ, เมนูตามชื่อ, ตำแหน่งที่ ต้องไม่เปลี่ยนแปลง, และเครื่องดื่มเฉพาะของวันนี้ สี่งาน, สี่ภาชนะ — การเลือก ระหว่างสิ่งเหล่านี้คือการตัดสินใจออกแบบของหน่วย 1.5

ลิสต์ คือลำดับที่มีลำดับและแก้ไขได้: marks = [72, 85, 61], มีดัชนี เริ่มต้นที่ 0, ตัดด้วย marks[1:3]. ไดร์กชันนารี เชื่อมโยงคีย์กับค่า: price = {"flat white": 3.5, "tea": 2.8}, อ่านและเขียนด้วยคีย์. ทูนเปิล เป็นลำดับค่าคงที่ (53.1, -1.5) — เหมาะสำหรับการกำหนดพิกัดที่ไม่ควรเปลี่ยนไป A set เก็บองค์ประกอบที่เป็นเอกลักษณ์โดยไม่มีการจัดเรียง: {"a", "b"}

marks = [72, 85, 61]
marks.append(90)               # [72, 85, 61, 90]
best = max(marks)              # 90
average = sum(marks) / len(marks)   # 77.0
ชื่อ a และ b ชี้ไปยังวัตถุลิสต์เดียวกัน ซึ่งเก็บ 1 และ 2 หลัง b.append(2)
การอ้างอิงร่วมกัน: สองชื่อ, ลิสต์เดียว

การกำหนดค่าไม่ได้คัดลอกข้อมูล: a = [1]; b = a; b.append(2) ทำให้ a ชี้ไปยัง [1, 2] — คือรายการ ถูกแชร์กัน, แต่ชื่อนั้นไม่ใช่. การอ้างอิงร่วมกันนี้คือกล่องของหน่วย 1.1 อีกครั้ง, ตอนนี้ with two tags on one box, and it explains the next rule too: mutating a list while วนลูปผ่านมันจะทำให้ข้ามบางองค์ประกอบ — ให้วนลูปผ่านสำเนา (for m in marks[:]) หรือสร้าง รายการใหม่แทน

ตัวอย่างวิธีทำ 1 (นับตามหมวดหมู่)

orders = ["tea", "flat white", "tea", "tea", "mocha"]
tally = {}
for item in orders:
    tally[item] = tally.get(item, 0) + 1
print(tally)   # {'tea': 3, 'flat white': 1, 'mocha': 1}

.get(key, 0) ของพจนานุกรมให้ 0 สำหรับคำที่ปรากฏครั้งแรก — สำนวนที่ ทำให้การนับเป็นบรรทัดเดียว ตรวจสอบด้วยมือ: คำว่า "tea" ปรากฏสามครั้งใน orders, และจำนวนรวมที่พิมพ์ไว้ยืนยันสิ่งนี้

ตัวอย่างวิธีทำ 2 (สินค้าขายดีที่สุดของวัน, ปรับปรุง). สร้างบนพื้นฐานของจำนวนรวม, รายงานรายการที่มีค่าสูงสุดและจำนวน:

best = max(tally, key=tally.get)
print(best, tally[best])       # tea 3

การโต้แย้งของ key=tally.get แนะนำให้ max เปรียบเทียบรายการตามจำนวนมากกว่า การเรียงตามตัวอักษร — เทคนิค key= เดียวกันนี้จัดอันดับชุดข้อมูลใดๆ ตามเกณฑ์ใดก็ได้. การ ปรับปรุงมีความสำคัญ: โปรแกรมจริงจะเชื่อมโยงเครื่องมือเหล่านี้เข้าด้วยกัน (นับก่อน แล้วจัดลำดับ) แทนที่จะ สแกน orders ซ้ำสองรอบ.

การเลือกชนิดของข้อมูลคือข้อตัดสินใจในการออกแบบ: ลำดับ + ค่าซ้ำ → ลิสต์; ค้นหาโดย ชื่อ → ดิกชันนารี; กลุ่มแบบตายตัว → โทเปิล; ความไม่ซ้ำ → เซ็ต.

ข้อผิดพลาดที่พบบ่อย. การเข้าถึง tally[item] += 1 ที่คีย์ซึ่งยังไม่มีอยู่ในดิกชันนารี (KeyError) — .get คือทางออก. การคาดหวังว่าเซ็ตหรือดิกชันนารีจะจดจำลำดับ. การสันนิษฐานว่า b = a ทำสำเนาลิสต์; มันเป็นการอ้างอิงถึงลิสต์นั้นแทน.

สรุป: โครงสร้างสี่ประเภทสำหรับงานสี่อย่าง; การกำหนดค่าแบ่งปันการอ้างอิง ไม่เคยทำสำเนา; .get ทำให้การนับปลอดภัย; key= ทำให้การจัดลำดับเป็นบรรทัดเดียว.

คำศัพท์ ฝึกฝน
English ไทย
list/lɪst/ รายการ
dictionary/ˈdɪkʃənəri/ ไดคชั่นนารี
tuple/ˈtuːpl/ ทูเปิล
aliasing/ˈeɪlɪəsɪŋ/ การอ้างอิงร่วมกัน
1.6

ไฟล์และความผิดพลาด

ร้านกาแฟปิดลง, ไฟดับ — และจำนวนรวมของเช้าจะอยู่ได้ก็ต่อเมื่อโปรแกรม เขียนมันลงไดรฟ์แล้ว. ไฟล์ มีอายุยืนยาวกว่าโปรแกรม. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") เขียน (และเคลียร์ข้อมูลก่อน —อุบัติเหตุคลาสสิก)

with open("log.txt", "w") as f:
    f.write("opened\n")

with open("log.txt") as f:
    for line in f:
        print(line.rstrip())     # rstrip removes the trailing newline

บล็อก with คือรั้วกันตก: ไม่ว่าอะไรจะเกิดขึ้นภายในนั้น ไฟล์จะถูกปิด การอ่านจะให้บรรทัด พร้อม ตัวขึ้นบรรทัดใหม่ ดังนั้น .rstrip() จึงควรอยู่ในทุกวงจร

ข้อผิดพลาดคือค่าที่ Python ทิ้งออกมาเป็น ข้อยกเว้น: FileNotFoundError, ValueError, KeyError, TypeError. try/except จับประเภทที่มีชื่อเฉพาะ — ห้ามจับ except: แบบเปลือยที่บดบังข้อผิดพลาดจริง

ตัวอย่างวิธีทำ 1 (ไฟล์ตั้งค่าที่หายไป) การอ่านขนาดข้อความจากไฟล์ตั้งค่า ซึ่งอาจไม่มีอยู่หรือไม่เก็บตัวเลขทั้งจำนวน:

try:
    with open("settings.txt") as f:
        size = int(f.read().strip())
except FileNotFoundError:
    size = 12                    # no file yet: a sensible default
except ValueError:
    size = 12                    # the file holds "big", not a number
print("Text size", size)

รันโค้ดสามครั้ง — ด้วย 16 ในไฟล์, ไม่มีไฟล์, และด้วย big ใน ไฟล์ — แล้วโปรแกรมจะพิมพ์ขนาดที่ใช้ได้ทุกครั้ง ผ่านสามช่องทางที่แตกต่างกัน นั่นคือรูปแบบของโค้ดที่ทนทาน: ทางเลือกเดียวที่สมเหตุสมผล หลายเส้นทางนำไปสู่จุดนั้น จัดการข้อผิดพลาดที่คุณคาดหวังไว้ เมื่อคุณสามารถตอบโต้ได้ ส่วนที่เหลือให้พังทลายด้วยเสียงดัง

ตัวอ่านค่าการตั้งค่า: บล็อก try เปิดออก อ่านและแปลงค่า; มัน Either ทำงานสำเร็จ, หรือเกิด FileNotFoundError หรือ ValueError จะใช้ค่าเริ่มต้น 12; ทั้งสามเส้นทางพบกันและโปรแกรมดำเนินต่อไป

ตัวอย่างวิธีทำ 2 (วงจรอินพุตที่ทนทานแบบเพิ่มประสิทธิภาพ) ถามจนกว่าผู้ใช้จะพิมพ์ จำนวนเต็ม:

def read_count():
    while True:
        try:
            return int(input("How many? "))
        except ValueError:
            print("Whole numbers only.")

print(read_count())

ป้อนเข้าให้ x แล้ว 4: รอบแรกจะเกิดข้อผิดพลาด ถูกจับ พิมพ์ข้อความ และ while True ทบทวนอีกครั้ง; การเดินทางครั้งที่สองของ int(...) สำเร็จและ return ออกจากกระบวนการ ออกจากวงจร และ ฟังก์ชันไปพร้อมกันในขั้นตอนเดียว สามองค์ประกอบนี้รวมกันที่นี่ — วงจร (1.3), สัญญาของฟังก์ชัน (1.4) และข้อยกเว้น (1.6) — ซึ่งเป็นหัวใจของการ เพิ่มประสิทธิภาพ

ข้อผิดพลาดทั่วไป open(path, "w") ในเส้นทางที่ผิด: ไฟล์ถูกสร้างหรือ emptied ก่อนที่คุณจะ Notice จับ Exception อย่างกว้างขวางและดำเนินต่อด้วย state. Forgetting .rstrip(), so output shows double-spaced lines.

Summary: with open(...) for both reading and writing; "w" empties first; ข้อยกเว้นคือค่า — จับประเภทที่มีชื่อที่คุณคาดหวังไว้ fallback อย่างมีเหตุผล ปล่อยให้ the rest crash.

คำศัพท์ ฝึกฝน
English ไทย
file/faɪl/ ไฟล์
exceptions ข้อยกเว้น
decomposition/ˌdiːkɒmpəˈzɪʃn/ การแยกย่อย
1.7

การแยกส่วน การทดสอบ และการแก้ไขข้อผิดพลาด

สองทีมเขียนแอปพลิเคชันเดียวกันทีมหนึ่งส่งมอบภายในหนึ่งสัปดาห์อีกทีมหนึ่งติดขัดไปทั้งเดือน ความ แตกต่างไม่ใช่ความเร็วในการพิมพ์ แต่คือโค้ดของทีมที่รวดเร็วถูกสร้างจากชิ้นส่วนเล็กๆ ที่แต่ละชิ้นสามารถ พิสูจน์ความถูกต้องได้ ในขณะที่ทีมที่ช้าต้องมาแก้ข้อผิดพลาดของแอปทั้งตัวพร้อมกัน Unit Testing 1.7 คือวินัยนี้เอง

การแยกส่วน แบ่งปัญหาออกเป็นฟังก์ชันที่รับผิดชอบงานเดียวเท่านั้น ซึ่งแต่ละฟังก์ชันมีขนาดเล็ก พอที่จะทดสอบแยกต่างหาก การทดสอบ เปรียบเทียบผลลัพธ์จริงของฟังก์ชันกับสิ่งที่คาดไว้ โดยเน้นที่จุดขอบเขต เช่น ค่าศูนย์ ค่าหนึ่ง ค่าว่าง ค่าลบ และค่ามหาศาล ค่าที่คาดหวังไว้ ที่ขอบเขต: ศูนย์, หนึ่ง, ว่าง, ค่าลบ, ค่ามหาศาล

ลูปที่ขับเคลื่อนด้วยการทดสอบ: เขียนทดสอบที่ล้มเหลวเพื่อระบุสัญญา; รันดูว่าล้มเหลวด้วยเหตุผลที่ถูกต้อง; เขียนส่วนที่ง่ายที่สุดที่ผ่านได้; รันซ้ำจนสีเขียว; Refactor โดยใช้ทดสอบเป็นกรอบ
ลูปที่ขับเคลื่อนด้วยการทดสอบ: แดง เขียว Refactor
def add_fee(amount):
    return amount + 3

def order_total(unit_pence, quantity):
    return add_fee(unit_pence * quantity)

assert order_total(250, 4) == 1003
assert order_total(250, 0) == 3      # the zero-quantity boundary
assert order_total(0, 4) == 3

An assert that never fires is a passing test. Write the tests before you write the body when you can — they are the contract made executable, and the loop above (แดง → เขียว → refactor) คือนิสัยนั้นที่ดำเนินไปอย่างต่อเนื่อง

ตัวอย่างแบบฝึกหัด 1 (ขยายการทดสอบไปกับฟังก์ชัน). ขยาย order_total ด้วย ส่วนลด 10% สำหรับราคา £10 — เขียนทดสอบก่อนที่ขอบเขตใหม่:

def order_total(unit_pence, quantity):
    subtotal = add_fee(unit_pence * quantity)
    if subtotal >= 1000:             # pence: over £10
        subtotal = int(subtotal * 0.9)
    return subtotal

assert order_total(200, 4) == 803    # under £10 — unchanged
assert order_total(250, 4) == 902    # 1003 pence → 10% off → 902.7 → 902
assert order_total(250, 5) == 1127   # 1253 pence → 1127.7 → 1127

ตรวจสอบการทดสอบกลางด้วยตนเอง: ผลรวมย่อย 1003, ลดลงสิบเปอร์เซ็นต์จะได้ 902.7, และ int(...) ตัดทอนลงเป็น 902. ระวัง assertion แรกด้วย: ที่ 200 เพนนิ์และสี่ ถ้วย ผลรวมยังคงเป็น 803 — การทดสอบขอบเขตพิสูจน์ได้ว่าส่วนลดถูกใช้ และว่าไม่ถูกใช้ก่อนกำหนด.

ตัวอย่างที่คำนวณแล้ว 2 (แบ่งข้อผิดพลาดด้วยพิมพ์, ยกระดับ). จำนวนนับจาก 1.5 พิมพ์ {'tea': 3} แต่บรรทัดขายดีพิมพ์เครื่องดื่มผิด วิธีทำ: (1) ทดสอบซ้ำด้วยอินพุตที่ล้มเหลวที่มีขนาดเล็กที่สุด; (2) พิมพ์ค่าที่ขอบเขต ระหว่างส่วนที่เชื่อถือได้กับส่วนที่สงสัย — ที่นี่คือจำนวนนับก่อน max; (3) แก้ไขหนึ่งอย่าง; (4) รันการทดสอบอีกครั้ง การย้ายคำสั่งพิมพ์กึ่งกลางโค้ด ทุกครั้งที่รันจะลดพื้นที่ค้นหาครึ่งหนึ่ง: สี่คำสั่งพิมพ์สามารถหาข้อผิดพลาดในโปรแกรมขนาดสิบหกบรรทัด อย่างไรก็ตาม มันซ่อนตัวได้ดีเพียงใด

การแก้ไขข้อผิดพลาด คือการหาคำแตกต่างระหว่างสิ่งที่เราเชื่อว่าโค้ดควรทำ กับสิ่งที่มันทำจริง — คำพิมพ์ที่ขอบเขตความเชื่อ/ความสงสัยคือเครื่องมือ

ข้อผิดพลาดทั่วไป. ทดสอบเฉพาะเส้นทางที่ประสบความสำเร็จ: ขอบเขต (ศูนย์, หนึ่ง, ว่าง, ลบ) คือจุดที่โปรแกรมพัง การเขียนโค้ดหนึ่งสัปดาห์ก่อนการทดสอบครั้งแรก, แล้วมาแก้ข้อผิดพลาดทีหลัง การแก้ไขสองอย่างพร้อมกัน ทำให้ผลลัพธ์ทั้งสองไม่สามารถ สิ่งใดอย่างแท้จริง

สรุปได้: แยกย่อยจนแต่ละส่วนทดสอบได้ด้วยตัวเอง; assert ขอบเขตทันทีที่มีฟังก์ชัน; เขียนการทดสอบก่อนเมื่อทำได้; แก้ไขข้อผิดพลาดด้วยการแบ่งครึ่งโดยใช้คำสั่งพิมพ์ที่ขอบเขตความเชื่อ — ทักษะนี้คือรากฐานของปริญญาทั้งหมด นี่คือรายวิชาแรกในหลักสูตรวิศวกรรมไฟฟ้าระดับมหาวิทยาลัย: วงจรกระแสตรง ที่ขอบเขตความไว้วางใจ — นิสัยที่ระดับปริญญาทั้งหมดสร้างบนพื้นฐานนี้

คำศัพท์ ฝึกฝน
English ไทย
testing/ˈtestɪŋ/ การทดสอบ
debugging/ˈdiːbʌɡɪŋ/ การดีบัก

บทเรียนเชิงโต้ตอบสำหรับหัวข้อนี้

ทำทีละขั้นตอน พร้อมแบบฝึกหัดตรวจสอบผลทันที

หัวข้อเพิ่มเติมใน วิทยาการคอมพิวเตอร์ มหาวิทยาลัย

เข้าสู่ระบบหรือสร้างบัญชี

IGCSE, A-Level & AP