2.25. การจัดการข้อผิดพลาด

ปัญหา runtime ส่วนใหญ่ใน Python แสดงออกมาเป็น exceptions ซึ่งเป็นวิธีรายงานที่มีชื่อและโครงสร้างว่ามีบางอย่างผิดพลาด ValueError, TypeError, KeyError, OSError, MemoryError เป็นตัวอย่างทั้งหมด แต่ละอันเป็น class และการ raise หนึ่งอันจะหยุดการเรียกปัจจุบันและค้นหา handler ในโค้ดโดยรอบ

2.25.1. try / except

ครอบบล็อกโค้ดด้วย try เพื่อดักจับ exception ที่เกิดขึ้น:

try:
    n = int(input_text)
except ValueError:
    n = 0

หากการแปลง int(...) ล้มเหลว การควบคุมจะกระโดดไปยังบล็อก except แทนที่จะส่งต่อข้อผิดพลาด หาก input_text เป็น string จำนวนเต็มที่ถูกต้อง บล็อก except จะถูกข้าม

try เดียวสามารถมีบล็อก except หลายบล็อก แต่ละบล็อกดักจับข้อผิดพลาดประเภทต่างกัน:

try:
    value = data[key]
except KeyError:
    value = None
except TypeError:
    value = -1

Python จับคู่ตามลำดับ อันแรกที่ class exception ตรงกันจะจัดการปัญหา การดักจับ Exception (base class สำหรับเกือบทุกอย่าง) จัดการ ข้อผิดพลาดใดก็ได้ สงวนไว้สำหรับชั้นนอกสุดของโปรแกรมที่ทางเลือกคือการ crash

Warning

except: แบบเปล่า (ไม่มี class หลัง keyword) ยังดักจับ KeyboardInterrupt ด้วย ซึ่งเป็น exception ที่ IDE ส่งเมื่อคุณกดปุ่ม stop เพื่อหยุดสคริปต์ที่กำลังทำงาน ลูปที่ครอบด้วย except: pass แบบเปล่าจะกลืน interrupt และทำงานต่อ ทำให้ไม่มีทางหยุดสคริปต์ได้ยกเว้นการรีบูต

เลือกใช้ except Exception: แทน except: แบบเปล่าเมื่อต้องการดักจับในวงกว้างจริงๆ KeyboardInterrupt สืบทอดจาก BaseException ไม่ใช่ Exception ดังนั้น except Exception: จะยังคงให้ปุ่ม stop ทำงานได้

2.25.1.1. การตรวจสอบ exception

หากต้องการอ่านข้อความที่แนบมากับ exception ให้ตั้งชื่อด้วย as:

try:
    f = open("data.txt")
except OSError as e:
    print("could not open file:", e)

ตัวแปรที่ผูกด้วย as ใช้ได้เฉพาะภายในบล็อก except เท่านั้น

2.25.2. else และ finally

บล็อก try มี extras แบบไม่บังคับสองอย่าง

else ทำงานเฉพาะเมื่อ try เสร็จสิ้นโดยไม่มีการ raise:

try:
    value = compute()
except ValueError:
    print("bad input")
else:
    print("got", value)

การวาง "สิ่งที่ต้องทำเมื่อสำเร็จ" ไว้ใน else ทำให้บล็อก try แคบลง มีเพียงบรรทัดที่อาจล้มเหลวเท่านั้นที่อยู่ใน try

finally ทำงานตอนท้าย ไม่ว่าจะเกิดอะไร ไม่ว่า try จะสำเร็จ ถูก raise และจัดการแล้ว หรือถูก raise และกำลังจะส่งต่อ:

try:
    do_work()
finally:
    cleanup()
Flow diagram showing the four possible paths through try / except / else / finally: success goes to else then finally; a caught exception goes to except then finally; an uncaught exception goes to finally then propagates.

finally ทำงานเสมอ else ทำงานเฉพาะในเส้นทางที่ไม่มี exception

สำหรับ pattern การ acquire/release ส่วนใหญ่ เลือกใช้ context manager แทนคู่ try / finally เพราะ resource จัดการ cleanup ของตัวเองได้

2.25.3. Built-in exceptions ที่พบบ่อย

รายการสั้นๆ ของ exceptions ที่คุณจะพบบ่อย:

  • ValueError -- ประเภทถูกต้อง ค่าไม่ถูกต้อง (bytes([300]) -- 300 เป็นประเภทที่ถูกต้อง แต่อยู่นอกช่วง byte ที่ถูกต้อง 0..255)

  • TypeError -- ประเภทผิดทั้งหมด (len(42))

  • KeyError -- key ที่ไม่มีใน dict

  • IndexError -- index เกินขอบเขตของลำดับ

  • AttributeError -- เข้าถึง attribute ที่ไม่มีอยู่ ("abc".foo)

  • OSError -- ความล้มเหลวของ filesystem หรือ I/O

  • MemoryError -- heap หมด บน runtime ที่มีหน่วยความจำจำกัด สิ่งนี้อาจเกิดขึ้นระหว่างการทำงานปกติ ไม่ใช่แค่ในกรณีสุดขีด