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

Custom Errors

Exception built-in ของ Python (ValueError, TypeError, RuntimeError, …) มีความหมายทั่วไปมาก เมื่อโค้ด raise ValueError caller ไม่รู้ว่ามาจาก input ของผู้ใช้, ไฟล์ config ที่เสีย, หรือ network response

Custom exception class ให้ error ของคุณมีประเภทที่ชัดเจน ให้ caller จับได้ตามต้องการ, เพิ่ม attribute ที่มีโครงสร้าง (เช่น HTTP status code หรือชื่อ field), และบอกได้ว่าแต่ละส่วนของแอปพลิเคชันอาจเกิดอะไรขึ้น

Custom exception ขั้นต่ำคือ class ที่สืบทอดจาก Exception:

class AppError(Exception):
"""Base class for all application errors."""

แค่นี้ก็พอสำหรับ raise และ catch ตามชื่อได้:

raise AppError("something went wrong")

Override __init__ เพื่อแนบข้อมูลเพิ่มเติมกับ exception:

class AppError(Exception):
def __init__(self, message: str, code: int = 0) -> None:
super().__init__(message) # ส่ง message ให้ Exception
self.code = code

Caller สามารถอ่าน err.code เพื่อรับข้อมูลที่มีโครงสร้างโดยไม่ต้อง parse string ข้อความ

สร้าง subclass สำหรับแต่ละหมวดหมู่ความผิดพลาด ทุก NotFoundError ก็เป็น AppError ด้วย ดังนั้น caller จับได้ที่ระดับความเฉพาะเจาะจงที่ต้องการ

class ValidationError(AppError):
def __init__(self, field: str, message: str) -> None:
super().__init__(f'Validation failed on "{field}": {message}', code=400)
self.field = field
class NotFoundError(AppError):
def __init__(self, resource: str, resource_id: int) -> None:
super().__init__(f"{resource} id={resource_id} not found", code=404)
self.resource = resource
self.resource_id = resource_id

การจับที่ระดับต่างกัน:

try:
raise ValidationError("email", "must contain @")
except ValidationError as e:
print(f"Field {e.field!r} invalid: {e}") # เฉพาะเจาะจง
except AppError as e:
print(f"App error [{e.code}]: {e}") # กว้างกว่า

str(e) คืน message ที่ส่งให้ super().__init__() นี่คือสิ่งที่แสดงเมื่อ print(e) หรือใช้ใน f-string

repr(e) คืน ExceptionClass(message) — ใช้สำหรับ logging

# --- base application error ---
class AppError(Exception):
"""Base class for all application errors."""
def __init__(self, message: str, code: int = 0) -> None:
super().__init__(message)
self.code = code
# --- specific subclasses ---
class ValidationError(AppError):
"""Raised when input validation fails."""
def __init__(self, field: str, message: str) -> None:
super().__init__(
f'Validation failed on field "{field}": {message}',
code=400,
)
self.field = field
class NotFoundError(AppError):
"""Raised when a requested resource does not exist."""
def __init__(self, resource: str, resource_id: int) -> None:
super().__init__(
f"{resource} with id={resource_id} not found",
code=404,
)
self.resource = resource
self.resource_id = resource_id
# --- catching at different levels ---
errors = [
ValidationError("email", "must contain @"),
NotFoundError("User", 42),
AppError("generic app failure", code=500),
]
for err in errors:
try:
raise err
except ValidationError as e:
print(f"[{e.code}] Validation on {e.field!r}: {e}")
except NotFoundError as e:
print(f"[{e.code}] {e.resource} not found (id={e.resource_id})")
except AppError as e:
print(f"[{e.code}] AppError: {e}")
# --- isinstance hierarchy ---
print()
e = ValidationError("name", "too short")
print(f"ValidationError? {isinstance(e, ValidationError)}")
print(f"AppError? {isinstance(e, AppError)}")
print(f"Exception? {isinstance(e, Exception)}")
print(f"BaseException? {isinstance(e, BaseException)}")
ทำไม custom application exception จึงควร subclass `Exception` แทน `BaseException`?
กำหนด hierarchy `NotFoundError -> AppError -> Exception` clause `except` ใดที่จับ `NotFoundError` ได้?
การเรียก `super().__init__(message)` ภายใน `__init__` ของ custom exception ทำอะไร?
มี `except ValidationError` ก่อน `except AppError` ใน try block และเกิด ValidationError ขึ้น clause ใดทำงาน?