Trong phần này chúng ta sẽ tìm hiểu về lỗi ngoại lệ (exception) trong Python.
Lỗi exception là lỗi xảy ra trong quá trình chạy (tiếng anh là run time error). Trong lập trình có 3 loại lỗi là lỗi biên dịch, lỗi ngữ nghĩa và lỗi ngoại lệ. Trong đó lỗi biên dịch dễ phát hiện nhất, lỗi ngoại lệ là lỗi khó phát hiện nhất. Ví dụ như bạn viết một chương trình máy tính bỏ túi gồm các chức năng cộng trừ nhân chia. Thoạt nhìn có vẻ như rất đơn giản, chỉ cần yêu cầu người dùng nhập vào 2 số và phép tính, thực hiện phép tính rồi trả lại kết quả cho người dùng. Nhưng chương trình này sẽ báo lỗi nếu người dùng thực hiện một phép tính chia với số chia là 0. Đó là loại lỗi chỉ xảy ra trong khi chương trình chạy, để phòng ngừa loại lỗi này thì chỉ có một cách là các lập trình viên phải đoán trước các trường hợp lỗi ngoại lệ có thể xảy ra và xử lý chúng trước trong code của mình. Nhưng vì bạn chẳng phải nhà tiên tri, bạn không thể nào đoán trước tất cả mọi thứ được 🙂 nên trên thực tế thì các phần mềm lớn vẫn thường có lỗi chứ không có phần mềm nào không có lỗi cả, công việc của chúng ta là cố gắng phát hiện lỗi và sửa lỗi thôi.
Trong Python và các ngôn ngữ lập trình cấp cao khác như Java, C#… các lỗi exception thường gặp được định nghĩa thành một lớp riêng, mỗi khi chương trình gặp các lỗi này trình thông dịch sẽ báo tên các lỗi đó ra màn hình.
>>> 3 / 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero
Trong ví dụ trên, chúng ta chạy trình thông dịch Python trong cmd và thực hiện phép tính 3 / 0. Chương trình trả về lỗi ZeroDivisionError.
def input_numbers(): a = float(input("Enter first number:")) b = float(input("Enter second number:")) return a, b x, y = input_numbers() print ("%d / %d is %f" % (x, y, x/y))
Trong đoạn code trên, chúng ta tính phép chia 2 số, nếu một trong 2 số là số 0 thì sẽ nảy sinh một lỗi exception.
Enter first number:2 Enter second number:0 Traceback (most recent call last): File "./zerodivision.py", line 12, in <module> print "%d / %d is %f" % (x, y, x/y) ZeroDivisionError: float division
Để xử lý lỗi exception thì chúng ta có 2 cách.
def input_numbers(): a = float(input("Enter first number:")) b = float(input("Enter second number:")) return a, b x, y = input_numbers() while True: if y != 0: print ("%d / %d is %f" % (x, y, x/y)) break else: print ("Cannot divide by zero") x, y = input_numbers()
Cách thứ nhất là chúng ta kiểm tra bằng các câu lệnh if...else.
Cứ thấy người dùng nhập vào số 0 thì chúng ta in câu thông báo và yêu câu nhập lại, không thì thực hiện phép tính và trả kết quả.
Enter first number:4 Enter second number:0 Cannot divide by zero Enter first number:5 Enter second number:0 Cannot divide by zero Enter first number:5 Enter second number:6 5 / 6 is 0.833333
Cách thứ hai là “bắt” các đối tượng exception có thể nảy sinh trong Python.
def input_numbers(): a = float(raw_input("Enter first number:")) b = float(raw_input("Enter second number:")) return a, b x, y = input_numbers() while True: try: print ("%d / %d is %f" % (x, y, x/y)) break except ZeroDivisionError: print ("Cannot divide by zero") x, y = input_numbers()
Để bắt exception thì chúng ta sử dụng câu lệnh try...except
. Trong ví dụ trên, sau từ khóa try là các câu lệnh thực hiện các công việc của chúng ta. Sau câu lệnh except là tên exception có thể xảy ra. Bên trong except là các câu thông báo lỗi mà chúng ta muốn in ra. Khi chạy ban đầu chương trình sẽ thực thi các câu lệnh bên trong try
, nếu có exception xảy ra thì câu lệnh except sẽ “bắt” exception đó rồi thực hiện phần thông báo lỗi.
except ValueError: pass except (IOError, OSError): pass
Bạn có thể “bắt” nhiều exception bằng cách dùng nhiều câu lệnh except hoặc đặt các exception bên trong một tuple.
Tham số đối tượng exception
Khi trình thông dịch báo lỗi exception, thì một đối tượng của lớp exception đó được tạo ra và bạn có thể tham chiếu đến nó bằng cách đặt một tham số thứ 2 vào sau tên của exception để lấy tham chiếu đến đối tượng này (thường chúng ta đặt tên là e
hoặc exception
).
try: 3/0 except ZeroDivisionError, e: print ("Cannot divide by zero") print ("Message:", e.message) print ("Class:", e.__class__)
Trong đối tượng exception có một thuộc tính tên là message
. Đây là câu thông báo lỗi có sẵn trong Python.
Cannot divide by zero Message: integer division or modulo by zero Class: <type 'exceptions.ZeroDivisionError'>
Cấu trúc phân cấp của exception
Hệ thống exception cũng được phân cấp, với lớp gốc là lớp Exception.
try: while True: pass except KeyboardInterrupt: print ("Program interrupted")
Trong đoạn code trên chúng ta có một vòng lặp vô tận. Nếu chúng ta bấm Ctrl+C, vòng lặp sẽ bị ngắt bởi lỗi KeyboardInterrupt
.
Exception BaseException KeyboardInterrupt
Lớp KeyboardInterrupt
là lớp kế thừa từ lớp BaseException
.
try: while True: pass except BaseException: print ("Program interrupted")
Chúng ta có thể bắt lỗi BaseException
thay vì bắt lỗi KeyboardInterrupt
.
Định nghĩa exception
Chúng ta có thể định nghĩa các lớp exception của riêng chúng ta.
class BFoundError(Exception): def __init__(self, value): print ("BFoundError: b character found at position %d" % value) string = "You make me want to be a better man." pos = 0 for i in string: if i == 'b': raise BFoundError(pos) pos = pos + 1
Các lớp exception mới đều phải được kế thừa từ lớp Exception.
Để giải phóng một exception thì chúng ta dùng từ khóa raise.
Trong ví dụ trên, chúng ta định nghĩa lớp BFoundError,
một string và một vòng lặp, chúng ta duyệt qua string, nếu tìm thấy kí tự b
trong chuỗi thì giải phóng exception.
BFoundError: b character found at position 20 Traceback (most recent call last): File "C:\Users\PhoCode\python\b.py", line 16, in <module> raise BFoundError, pos __main__.BFoundError
Từ khóa finally
Sau khối lệnh except
chúng ta có thể có từ khóa finally
và một số câu lệnh. Các câu lệnh trong khối finally
sẽ được thực thi cho dù có hay không có lỗi exception nào xảy ra. Thường thì các câu lệnh trong finally được dùng để thực hiện các công việc giải phóng tài nguyên…
f = None try: f = file('indaclub', 'r') contents = f.readlines() for i in contents: print (i,) except IOError: print ('Error opening file') finally: if f: f.close()
Đoạn code trên sẽ mở một file, nếu vì một lý do gì đó mà file không thể mở được thì lỗi IOError
sẽ xảy ra. Và cho dù có lỗi hay không có lỗi xảy ra thì chúng ta vẫn làm công việc đóng file trong phần finally.