Skip to content

บทที่ 6.1: Exception Handling (การจัดการ Error ด้วย try-except-finally)

เอกสารนี้อธิบายถึงกลไกการจัดการข้อผิดพลาด (Exception Handling) ในภาษา Python เพื่อป้องกันไม่ให้โปรแกรมหยุดทำงานกะทันหันเมื่อเกิด Error ในขณะ Runtime รวมถึงการสร้าง Custom Exception และการจัดการทรัพยากรด้วยบล็อก try-except-else-finally


1. โครงสร้าง try-except-else-finally

การจัดการ Exception ใน Python ใช้โครงสร้าง 4 ส่วนหลัก โดยแต่ละส่วนมีหน้าที่เฉพาะดังนี้:

  • try: ใส่โค้ดที่มีความเสี่ยงว่าจะเกิดข้อผิดพลาด
  • except: ใส่โค้ดที่จะทำงานเมื่อเกิดข้อผิดพลาดที่ระบุไว้
  • else: ใส่โค้ดที่จะทำงาน เมื่อไม่มีข้อผิดพลาดเกิดขึ้นเลย ในบล็อก try
  • finally: ใส่โค้ดที่จะ ทำงานเสมอ ไม่ว่าจะเกิดข้อผิดพลาดขึ้นหรือไม่ก็ตาม
def divide_numbers(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("ข้อผิดพลาด: ไม่สามารถหารด้วยศูนย์ได้")
    else:
        print(f"การคำนวณสำเร็จ: {result}")
    finally:
        print("จบกระบวนการคำนวณ\n")

divide_numbers(10, 2)
# Output:
# การคำนวณสำเร็จ: 5.0
# จบกระบวนการคำนวณ

divide_numbers(10, 0)
# Output:
# ข้อผิดพลาด: ไม่สามารถหารด้วยศูนย์ได้
# จบกระบวนการคำนวณ

2. การดักจับ Exception หลายรูปแบบ (Multiple Exceptions)

เราสามารถดักจับข้อผิดพลาดหลายชนิดได้โดยแยกบล็อก except ตามประเภทของ Exception หรือรวมไว้ใน Tuple เดียวกัน

def parse_and_get_element(data_list, index_str):
    try:
        index = int(index_str)
        value = data_list[index]
        print(f"ข้อมูลตำแหน่งที่ {index}: {value}")
    except ValueError:
        print("ข้อผิดพลาด: ข้อมูลอินพุตไม่ใช่ตัวเลขจำนวนเต็ม")
    except IndexError:
        print("ข้อผิดพลาด: ตำแหน่ง Index เกินขนาดของ List")
    except Exception as e:
        # ดักจับข้อผิดพลาดทั่วไปที่ไม่ได้ระบุไว้ข้างต้น
        print(f"เกิดข้อผิดพลาดไม่ทราบสาเหตุ: {type(e).__name__} - {e}")

data = ["A", "B", "C"]
parse_and_get_element(data, "1")   # Output: ข้อมูลตำแหน่งที่ 1: B
parse_and_get_element(data, "abc") # Output: ข้อผิดพลาด: ข้อมูลอินพุตไม่ใช่ตัวเลขจำนวนเต็ม
parse_and_get_element(data, "5")   # Output: ข้อผิดพลาด: ตำแหน่ง Index เกินขนาดของ List

3. การโยนข้อผิดพลาดและการสร้าง Custom Exception

3.1 การส่งผ่านข้อผิดพลาดด้วย raise

สามารถใช้คำสั่ง raise เพื่อสั่งให้เกิด Exception ขึ้นตามเงื่อนไขที่ต้องการได้

def set_age(age: int):
    if age < 0:
        raise ValueError("อายุไม่สามารถติดลบได้")
    print(f"ตั้งค่าอายุเป็น {age} เรียบร้อย")

try:
    set_age(-5)
except ValueError as e:
    print(f"การทำงานล้มเหลว: {e}")
# Output: การทำงานล้มเหลว: อายุไม่สามารถติดลบได้

3.2 การสร้าง Custom Exception Class

สร้าง Class ข้อผิดพลาดขึ้นเองโดยการสืบทอดจากคลาส Exception หลัก

class InsufficientFundsError(Exception):
    """Exception เมื่อยอดเงินในบัญชีไม่พอสำหรับถอน"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"ยอดเงินคงเหลือ ({balance}) ไม่เพียงพอสำหรับถอน ({amount})")

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

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance

account = BankAccount(1000)

try:
    account.withdraw(1500)
except InsufficientFundsError as e:
    print(f"เกิดข้อผิดพลาด: {e}")
    print(f"ยอดเงินคงเหลือปัจจุบัน: {e.balance}")
# Output:
# เกิดข้อผิดพลาด: ยอดเงินคงเหลือ (1000) ไม่เพียงพอสำหรับถอน (1500)
# ยอดเงินคงเหลือปัจจุบัน: 1000

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

เมื่อต้องการดักจับข้อผิดพลาดหนึ่งแล้วครอบด้วยข้อผิดพลาดชนิดใหม่ สามารถใช้ from เพื่อรักษา Context ของข้อผิดพลาดเดิมเอาไว้ใน Traceback ได้

def process_data(data):
    try:
        return int(data)
    except ValueError as err:
        raise RuntimeError("ไม่สามารถประมวลผลข้อมูลอินพุตได้") from err

# try:
#     process_data("invalid")
# except RuntimeError as e:
#     print(f"Error: {e}")
#     print(f"Original Cause: {e.__cause__}")