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

สร้าง Exception เอง & raise

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

นิยามข้อผิดพลาดของโดเมนงานเอง เพื่อให้ผู้เรียกจับและจัดการได้ตรงจุด

บางครั้ง error ที่มีอยู่ใน Python ไม่สื่อความหมายของปัญหาในงานเรา เช่น "เงินไม่พอ" หรือ "อายุไม่ถึง" การสร้าง exception ของตัวเองทำให้โค้ดอ่านง่ายและให้ผู้เรียกจับเฉพาะ error ของเราได้

raise — โยน exception ออกมาเอง

เมื่อเจอสถานการณ์ที่ไม่ควรดำเนินต่อ ให้ raise เพื่อหยุดและส่งสัญญาณว่าเกิดอะไร แทนที่จะคืนค่าแปลก ๆ เช่น -1 หรือ None ที่ผู้เรียกอาจลืมเช็ค

python
def set_age(age):
    if age < 0:
        raise ValueError(f"อายุติดลบไม่ได้: {age}")
    return age

set_age(-5)   # ValueError: อายุติดลบไม่ได้: -5

สร้าง custom exception class

สร้างคลาสที่สืบทอดจาก Exception ก็ได้ exception ใหม่แล้ว ตั้งชื่อให้สื่อความหมาย ลงท้ายด้วย Error ตามธรรมเนียม

python
class InsufficientFundsError(Exception):
    """ยอดเงินไม่พอสำหรับการถอน"""
    pass

class Account:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(
                f"ถอน {amount} ไม่ได้ มีแค่ {self.balance}"
            )
        self.balance -= amount
        return self.balance

acc = Account(100)
try:
    acc.withdraw(500)
except InsufficientFundsError as e:
    print(f"ถอนไม่สำเร็จ: {e}")   # จับเฉพาะ error ของเรา
ทำไมไม่ใช้ ValueError เฉย ๆ

custom exception ทำให้ผู้เรียกจับเฉพาะปัญหาของโดเมนได้ (เช่น except InsufficientFundsError) แยกออกจาก ValueError ทั่วไป โค้ดสื่อความหมายขึ้น และเพิ่มข้อมูลในคลาสได้ภายหลัง

เก็บข้อมูลเพิ่มใน exception

python
class ValidationError(Exception):
    def __init__(self, field, message):
        self.field = field            # เก็บว่า field ไหนผิด
        super().__init__(f"{field}: {message}")

try:
    raise ValidationError("email", "รูปแบบไม่ถูกต้อง")
except ValidationError as e:
    print(e.field)    # email  (เข้าถึงข้อมูลเพิ่มได้)
    print(e)          # email: รูปแบบไม่ถูกต้อง

Exception Chaining (raise ... from ...)

เมื่อจับ error หนึ่งแล้วโยน error อีกตัว ใช้ from เพื่อบอกที่มา ทำให้ traceback แสดงต้นตอเดิมด้วย — ดีต่อการ debug (เจอใน traceback บทถัดไป)

python
def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError("โหลด config ไม่ได้") from e
        # traceback จะแสดงทั้ง RuntimeError และต้นตอ FileNotFoundError

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

  • raise หยุดและส่งสัญญาณข้อผิดพลาด ดีกว่าคืนค่าแปลก ๆ ที่อาจลืมเช็ค
  • custom exception = class สืบจาก Exception ตั้งชื่อลงท้าย Error
  • เก็บข้อมูลเพิ่มใน exception ได้ผ่าน __init__
  • raise ... from ... ระบุที่มา ช่วย debug
แบบฝึกหัด

1) สร้าง custom exception ชื่อ AgeTooYoungError แล้วใช้ในฟังก์ชันสมัครสมาชิก (ต้องอายุ >= 18) 2) เพิ่มข้อมูล field ใน ValidationError 3) เขียนฟังก์ชันโหลดไฟล์ที่ใช้ raise ... from ... 4) อธิบายข้อดีของ custom exception เทียบกับ ValueError ทั่วไป