Notes & software courses · Free to learn
Aph's Blog
On this page

Exception เจาะลึก

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

จัดการข้อผิดพลาดให้ถูกวิธีด้วย try/except/else/finally — ไม่ใช่แค่ครอบ try กว้าง ๆ แล้วเงียบ

โปรแกรมจริงต้องเจอสถานการณ์ที่ผิดพลาด: ไฟล์ไม่มี, ผู้ใช้กรอกผิด, เน็ตหลุด การจัดการ exception ที่ดีทำให้โค้ด "พังอย่างสง่างาม" บอกได้ว่าพังเพราะอะไร แทนที่จะ crash เงียบ ๆ หรือกลืน error จนหา bug ไม่เจอ

ทบทวนจากคอร์สพื้นฐาน

คุณเคยเห็น try/except มาแล้ว หัวข้อนี้ลงลึกกว่า: จับเฉพาะชนิด, ลำดับ else/finally, exception hierarchy และวิธีที่มืออาชีพใช้จริง

โครงสร้างเต็ม: try / except / else / finally

ทั้ง 4 ส่วนมีหน้าที่ต่างกันชัดเจน — try คือโค้ดเสี่ยง, except จับเมื่อพัง, else รันเมื่อไม่พัง, finally รันเสมอ

python
try:
    x = int(input("กรอกเลข: "))   # โค้ดที่อาจพัง
except ValueError:
    print("นั่นไม่ใช่เลข")        # ทำเมื่อเกิด ValueError
else:
    print(f"ดีมาก ได้ {x}")        # ทำเมื่อ try ไม่พัง
finally:
    print("จบการทำงาน")            # ทำเสมอ ไม่ว่าพังหรือไม่
ส่วนทำงานเมื่อใช้ทำ
tryเริ่มเสมอโค้ดที่อาจเกิด error
exceptเกิด error ตรงชนิดจัดการ error
elsetry สำเร็จ ไม่พังโค้ดต่อที่ขึ้นกับ try สำเร็จ
finallyเสมอ (พังหรือไม่ก็ตาม)คืน resource / ปิดของ

จับเฉพาะชนิด — อย่าจับกว้างเกินไป

จับ exception ให้ตรงชนิดที่คาดไว้ และเก็บตัว exception ด้วย as เพื่อดูรายละเอียด จับหลายชนิดได้ทั้งแบบแยกและรวม

python
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("หารด้วยศูนย์ไม่ได้")
    except TypeError as e:
        print(f"ชนิดข้อมูลผิด: {e}")

# จับหลายชนิดพร้อมกันด้วย tuple
try:
    risky()
except (ValueError, KeyError) as e:
    print(f"เกิดข้อผิดพลาด: {e}")

Exception Hierarchy

exception ทุกตัวสืบทอดจาก Exception การจับ exception แม่จะจับลูกทั้งหมดด้วย — มีประโยชน์แต่ต้องระวังไม่ให้กว้างเกิน

python
# ValueError และ KeyError เป็นลูกของ Exception
# จับ Exception = จับเกือบทุกอย่าง (กว้างมาก ใช้เท่าที่จำเป็น)
try:
    risky()
except Exception as e:
    print(f"ผิดพลาด: {type(e).__name__}: {e}")
    # log ไว้แล้วอาจ re-raise ต่อ
    raise          # โยน exception เดิมต่อ (re-raise)
ห้าม: except เปล่า ๆ

อย่าเขียน except: เปล่า ๆ หรือ except Exception แล้ว pass เฉย ๆ เพราะมันกลืน error ทุกอย่างรวมถึง bug ของเราเอง ทำให้โปรแกรมผิดแบบเงียบ ๆ หา bug ไม่เจอ — จับให้ตรงชนิด และอย่างน้อยต้อง log

python
# ❌ ห้ามทำ: กลืนทุก error เงียบ ๆ
try:
    do_something()
except:
    pass        # bug ซ่อนอยู่ตรงนี้ไม่มีวันเจอ

# ✅ จับตรงชนิด + จัดการชัดเจน
try:
    do_something()
except FileNotFoundError as e:
    print(f"ไม่พบไฟล์: {e}")

สรุปหัวข้อนี้

  • try (เสี่ยง) / except (จับ) / else (สำเร็จ) / finally (เสมอ)
  • จับเฉพาะชนิดที่คาดไว้ เก็บด้วย as e เพื่อดูรายละเอียด
  • exception สืบทอดกัน — จับแม่ = จับลูกทั้งหมด
  • ห้าม except เปล่าหรือ pass เงียบ ๆ — มันซ่อน bug
แบบฝึกหัด

1) เขียนฟังก์ชันหารที่จัดการทั้ง ZeroDivisionError และ TypeError 2) เขียนโค้ดอ่านเลขจาก input ที่ใช้ครบ try/except/else/finally 3) ลองจับ (ValueError, KeyError) ด้วย tuple เดียว 4) อธิบายว่าทำไม except: pass เป็นอันตราย