2.26. การ raise ข้อผิดพลาด¶
ฟังก์ชันสามารถส่งสัญญาณปัญหาให้ผู้เรียกโดยการ raise exception คีย์เวิร์ดคือ raise:
def square_root(x):
if x < 0:
raise ValueError("square_root expects a non-negative number")
return x ** 0.5
การเรียก square_root(-1) จะหยุดที่บรรทัด raise กระโดดออกจากฟังก์ชัน และค้นหา except ที่ตรงกันในผู้เรียก หากไม่มีผู้เรียกดักจับ สคริปต์จะสิ้นสุดพร้อม traceback
2.26.1. ทำไมต้อง raise แทนการคืนค่า sentinel¶
สองวิธีในการรายงาน "input ไม่ถูกต้อง":
# signal with a sentinel
def square_root_or_none(x):
if x < 0:
return None
return x ** 0.5
# raise an exception
def square_root(x):
if x < 0:
raise ValueError("...")
return x ** 0.5
รูปแบบ exception มักดีกว่า:
ผู้เรียกต้อง จงใจ จัดการกรณีข้อผิดพลาด ไม่ว่าจะด้วย
tryหรือปล่อยให้ exception ส่งต่อ Sentinels ลืมได้ง่ายและเข้าใจผิดว่าเป็นผลลัพธ์ปกติได้ง่ายข้อความข้อผิดพลาดเดินทางไปกับ exception แต่วิธี sentinel ต้องแนบการวินิจฉัยไว้ที่อื่น
พฤติกรรมเริ่มต้นเมื่อ exception ไม่ถูกจัดการคือการ crash ดังๆ พร้อม traceback ที่ชี้ไปที่การเรียกที่ผิดพลาด การคืน
Noneแบบเงียบกลายเป็น bug ที่ซ่อนอยู่ในภายหลัง
ใช้ sentinels เฉพาะเมื่อ "ไม่พบ" เป็นผลลัพธ์ที่เกิดขึ้นปกติและไม่ใช่กรณีพิเศษ dict.get() คืน None เมื่อ key ไม่มี เพราะการค้นหาคาดว่าจะไม่พบได้บางครั้ง
2.26.2. Custom exception classes¶
หากต้องการ raise ปัญหาที่ผู้เรียกอาจต้องการแยกแยะจาก built-in errors ให้กำหนด subclass ของ Exception:
class ConfigError(Exception):
pass
def load_config(path):
try:
f = open(path)
except OSError as e:
raise ConfigError("missing config file: " + path)
try:
load_config("settings.json")
except ConfigError as e:
print("startup failed:", e)
body class ที่ว่างเปล่าใช้ได้ ชื่อเองคือสิ่งที่สำคัญ เพราะผู้เรียกดักจับตาม class จัดกลุ่ม errors ที่เกี่ยวข้องภายใต้ base เดียวกันหากผู้เรียกต้องการดักจับทั้งกลุ่มในบล็อกเดียว
2.26.2.1. การ re-raise¶
raise แบบเปล่าภายในบล็อก except จะ re-raise exception ปัจจุบันให้ส่งต่อไปยัง handler ถัดไป:
try:
do_work()
except Exception as e:
log(e)
raise # let it keep going
นี่คือรูปแบบที่ถูกต้องเมื่อฟังก์ชันต้องการ สังเกต ข้อผิดพลาด (log ไว้ นับ หรือยกเลิกการเปลี่ยนแปลงบางส่วน) โดยไม่ได้จัดการจริงๆ
2.26.3. เมื่อใดควรดักจับและเมื่อใดควรส่งต่อ¶
กฎเกณฑ์ที่มีประโยชน์:
ดักจับ exception ในระดับที่สามารถ กู้คืนได้อย่างมีความหมาย ไม่ว่าจะเป็นการแทนที่ด้วยค่าเริ่มต้น ลองใหม่ หรือข้าม input ที่ไม่ดี
ปล่อยให้ส่งต่อเมื่อไม่มีอะไรมีประโยชน์ให้ทำนอกจาก crash หรือเมื่อชั้นด้านบนเป็นอันที่รู้วิธีกู้คืน
ฟังก์ชันกลาง call stack ที่กลืน errors ทุกอย่างและคืนค่าอย่างเงียบทำให้ความล้มเหลวตามรอยไม่ได้ เลือกให้ exceptions เดินทางจนถึงโค้ดที่มีแผนจริงๆ