Python cơ bản
Python hướng đối tượng
Python nâng cao
Quảng cáo

Lỗi và xử lý ngoại lệ trong Python

Trong hướng dẫn này chúng ta sẽ đi tìm hiểu về các loại lỗi và cách xử lý ngoại lệ - Exception Handling trong lập trình Python

Giống nhiều ngôn ngữ lập trình khác thì trong Python cũng có nhiều cách xử lý các loại lỗi, bài này chúng ta sẽ cùng xem xét xem Python có những loại lỗi nào, và các xử lý ngoại lệ ra sao.

Các loại lỗi trong lập Python

Trong Python có thể phân biệt hai loại lỗi: lỗi cú pháp và lỗi thực thi.

Lỗi cú pháp trong Python

Lỗi cú pháp (syntax error) là loại lỗi phát sinh do viết code không tuân thủ theo quy tắc của ngôn ngữ. Lấy ví dụ:

Ví dụ

>>> print "hello" #lỗi cú pháp khi sử dụng hàm print()
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("hello")?

Trong Python 2, đây là một lệnh chính xác. Tuy nhiên đây lại là một lỗi cú pháp trong Python 3. Python 3 chuyển print() từ một lệnh (statement) trở thành một hàm thông thường. Do vậy phải gọi print() với cú pháp dành cho lời gọi hàm: print('Hello world!’).

Loại lỗi này thường gặp ở những người mới học lập trình Python. Đôi khi, chúng cũng xuất hiện do người viết code vô tình gõ nhầm. Lỗi này cũng hay xảy ra khi chạy code viết cho Python 2 trong Python 3 (và ngược lại) do hai phiên bản này có một số khác biệt về cú pháp.

Lỗi cú pháp thường dễ phát hiện và sửa chữa. Trình thông dịch Python hoặc các công cụ hỗ trợ của IDE có thể phát hiện ra lỗi cú pháp ngay từ lúc viết code. Khi gặp lỗi cú pháp, trình thông dịch Python sẽ đưa ra thông báo SyntaxError với thông tin chi tiết về lỗi liên quan.

Do đặc thù của ngôn ngữ thông dịch, dù có lỗi cú pháp, Python vẫn thực thi file script nhưng sẽ dừng lại ở vị trí lỗi. Điều này khác biệt với các ngôn ngữ biên dịch (như C#) – dừng biên dịch khi gặp lỗi cú pháp, chương trình chỉ chạy sau khi biên dịch.

Lỗi thực thi trong Python

Lỗi thực thi (runtime error) là loại lỗi phát sinh trong quá trình chạy chương trình. Lỗi thực thi cũng được gọi là ngoại lệ (exception). Ví dụ, trong chương trình bạn vô tình thực hiện phép chia cho 0, hoặc truy xuất một file không tồn tại trên ổ đĩa.

Đây là loại lỗi liên quan đến logic của chương trình. Loại lỗi này rất khó phát hiện. Ít nhất thì bạn không thể phát hiện chúng ở giai đoạn viết code. Chúng cũng chỉ xuất hiện khi thực thi trong những điều kiện nhất định.

Ứng với mỗi loại lỗi thực thi, Python sử dụng một class riêng để mô tả. Ví dụ, để mô tả lỗi chia cho 0, Python sử dụng class ZeroDivisionError. Python cung cấp rất nhiều class như vậy. Chúng được gọi chung là ngoại lệ xây dựng sẵn (built-in exception). Các class này có đặc điểm là tên gọi kết thúc bằng ‘Error’.

Nếu không nhớ, hãy đọc lại bài học về class trong Python.

Một dạng “lỗi” đặc biệt của Python được gọi là lỗi đánh giá (assert error). Đây là một loại “lỗi” liên quan đến việc giá trị của một biểu thức không đạt yêu cầu.

Ví dụ, bạn yêu cầu người dùng nhập tuổi. Do tuổi phải là một số nguyên dương, nếu người dùng nhập một giá trị âm, đây có thể xem là một assert error.

Assert error được dùng trong debug và test. Python cung cấp một cú pháp riêng cho assert error.

Cơ chế xử lý ngoại lệ mặc định của Python

Một cách thức xử lý chung mà Python (và các ngôn ngữ lập trình khác) áp dụng khi gặp loại lệ là dừng thực hiện chương trình và phát ra thông tin về lỗi mà nó gặp phải. Đây là cách thức xử lý ngoại lệ (exception handling) mặc định của Python.

Trong cơ chế xử lý ngoại lệ mặc định, Python sẽ (1) dừng thực hiện chương trình ở lệnh lỗi, (2) tạo object của class exception tương ứng, (3) in ra thông báo lỗi từ object exception với thông tin chi tiết về vị trí và loại lỗi.

Ví dụ, khi gặp phải lỗi chia cho 0, Python sẽ dừng thực thi tại dòng lệnh lỗi và phát ra thông báo ZeroDivisionError: division by zero. Cụ thể hơn, nếu bạn chạy file script sau:

Ví dụ

a = 10
b = 0
print(a/b)
print(a)
print(b)

Bạn sẽ gặp thông báo lỗi:

Ví dụ

Traceback (most recent call last):
File "exception.py", line 3, in
print(a/b)
ZeroDivisionError: division by zero

Theo thông báo này, trong file exception.py, dòng thứ 3, lệnh print(a/b) gặp lỗi chia cho 0.

Từ thông báo này bạn có thể biết chính xác lỗi ở đâu và lỗi gì. Nó rất có ích ở giai đoạn debug chương trình.

Lớp ngoại lệ xây dựng sẵn trong Python

Ứng với mỗi loại lỗi thực thi, Python sử dụng một class riêng để mô tả. Ví dụ, để mô tả lỗi chia cho 0, Python sử dụng class ZeroDivisionError. Để mô tả lỗi truy xuất file không tồn tại, Python sử dụng class FileNotFoundError.

Python cung cấp rất nhiều class như vậy. Chúng được gọi chung là ngoại lệ xây dựng sẵn (built-in exception). Các class này có đặc điểm là tên gọi kết thúc bằng ‘Error’.

Dưới đây là một số lớp ngoại lệ thường gặp.

IndexError – xuất hiện khi truy xuất phần tử của danh sách theo chỉ số

Ví dụ

>>> # IndexError - xuất hiện khi truy xuất phần tử của danh sách theo chỉ số
>>> L1=[1,2,3]
>>> L1[3] # không có chỉ số 3
Traceback (most recent call last):
File "<pyshell#18>", line 1, in <module>
L1[3]
IndexError: list index out of range

ModuleNotFoundError – xuất hiện khi import một module không tồn tại

Ví dụ

>>> # ModuleNotFoundError - xuất hiện khi import một module không tồn tại
>>> import notamodule # không có module này
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
import notamodule
ModuleNotFoundError: No module named 'notamodule'

ImportError – xuất hiện khi định danh trong lệnh import không chính xác

Ví dụ

>>> from math import cube # không có đối tượng nào tên là cube trong module math
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
from math import cube
ImportError: cannot import name 'cube'

TypeError – xuất hiện khi thực hiện phép toán trên những kiểu dữ liệu không phù hợp

Ví dụ

>>> '2'+2 # không thể cộng một xâu với một số
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
'2'+2
TypeError: must be str, not int

ValueError – xuất hiện khi biến đổi kiểu

Ví dụ

>>> int('xyz') # không thể chuyển đổi chuỗi 'xyz' thành kiểu số
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
int('xyz')
ValueError: invalid literal for int() with base 10: 'xyz'

NameError – xuất hiện khi sử dụng một định danh chưa được định nghĩa

Ví dụ

>>> age # định danh 'age' chưa được định nghĩa
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
age
NameError: name 'age' is not defined

Danh sách tất cả các ngoại lệ xây dựng sẵn của Python trên trang tài liệu chính thức của Python: https://docs.python.org/3.8/library/exceptions.html

Tự xử lý ngoại lệ với try .. except

Cách thức xử lý ngoại lệ mặc định chỉ phù hợp ở giai đoạn debug. Nếu để cơ chế này hoạt động trên ứng dụng đã triển khai sẽ rất không phù hợp. Nó tạo cảm giác thiếu ổn định cho ứng dụng. Ngoài ra, người dùng cuối thường cũng không biết làm gì với các thông báo lỗi đó.

Vì vậy, trước khi triển khai ứng dụng, bạn cần xây dựng cơ chế xử lý ngoại lệ riêng để đảm bảo ứng dụng hoạt động ổn định.

Cơ chế xử lý ngoại lệ riêng sẽ ngắt chế độ xử lý ngoại lệ mặc định trên một nhóm code nhất định và thay thế bằng cách thức xử lý khác do người lập trình tự xây dựng.

Python cung cấp cú pháp try .. except để bạn tự mình xử lý ngoại lệ. Cú pháp chung của cấu trúc này như sau:

Ví dụ

try:
    # thực hiện các lệnh có nguy cơ lỗi
except:
    # thực hiện nếu phát sinh lỗi trong khối try
else:
    # thực hiện nếu KHÔNG phát sinh lỗi trong khối try
finally:
    # LUÔN thực hiện, dù trong khối try có phát sinh lỗi hay không

Cấu trúc này bao gồm 4 khối: try, except, else, finally. Trong đó try và except là bắt buộc, else và finally là tùy chọn.

Khối try chứa các lệnh nguy hiểm. Lệnh nguy hiểm là những lệnh tiềm ẩn khả năng phát sinh lỗi thực thi. Ví dụ, phép chia là một lệnh nguy hiểm vì tiềm ẩn khả năng chia cho 0. Truy xuất file là một lệnh nguy hiểm vì tiềm ẩn khả năng truy xuất file không tồn tại. v.v.. Cấu trúc try .. except bắt buộc phải có và chứa duy nhất 1 khối try.

Khối except chứa các lệnh sẽ được thực thi nếu phát sinh lỗi trong khối try. Cấu trúc này bắt buộc phải có ít nhất một khối except. Có thể xây dựng nhiều khối except trong cùng một cấu trúc try .. except. Chúng ta sẽ nói kỹ hơn về cách xây dựng khối except trong phần tiếp theo của bài học.

Khối else chứa các lệnh sẽ được thực thi nếu KHÔNG phát sinh lỗi trong khối try. Nói cách khác, khối else và khối except luôn đối lập nhau: nếu đã thực hiện except (có lỗi) thì không thực hiện else; nếu không thực hiện except (không có lỗi) thì sẽ thực hiện else. Khối else là không bắt buộc.

Khối finally chứa các lệnh luôn luôn được thực thi, bất kể có lỗi hay không. Đây cũng là khối không bắt buộc.

Sử dụng cấu trúc try .. except

Ở phần trước chúng ta đã nói đến lý thuyết chung về cấu trúc try .. except. Tiếp theo đây chúng ta sẽ nói chi tiết về cách sử dụng cấu trúc này.

Hãy cùng thực hiện một ví dụ:

Ví dụ

try:
    a=5
    b='0'
    print(a/b)
except:
    print('Xảy ra lỗi gì đó.')
print("Ngoài khối try .. except.")

Đây là ví dụ sử dụng cấu trúc try .. except cơ bản nhất. Cách sử dụng này bắt tất cả các loại lỗi có thể phát sinh trong khối try. Nói cách khác, lối sử dụng này sẽ thực thi khối except nếu có bất kỳ lỗi gì phát sinh trong khối try. Tuy nhiên, ở trong khối except không biết bất kỳ thông tin gì về lỗi xảy ra.

Cách sử dụng này thực tế sẽ ẩn đi bất kỳ lỗi thực thi nào. Nhìn chung bạn không nên sử dụng lối viết này.

Hãy xem một ví dụ khác:

Ví dụ

try:
    a=5
    b='0'
    print (a+b)
except TypeError:
    print('Phép toán không hợp lệ')
print("Ngoài khối try .. except.")

Sự khác biệt của lối sử dụng này là có thêm tên kiểu ngoại lệ trong phần except.

Với lối viết này chỉ những lỗi liên quan đến TypeError (lỗi không đồng bộ kiểu trong phép toán) mới bị bắt. Các loại lỗi khác sẽ bị bỏ qua. Nghĩa là, chỉ khi nào phát sinh lỗi TypeError thì khối code except mới được thực thi. Nếu phát sinh lỗi khác thì vẫn thực hiện cơ chế xử lý lỗi mặc định của Python.

Bạn cũng có thể ghép nhiều khối except với nhau như sau:

Ví dụ

try:
    a=5
    b=0
    print (a/b)
except TypeError:
    print('Unsupported operation')
except ZeroDivisionError:
    print ('Division by zero not allowed')
print ('Out of try except blocks')

Khi này mỗi khối except sẽ chịu trách nhiệm cho một lỗi cụ thể.

Bạn có thể sử dụng thông tin chi tiết của lỗi như sau:

Ví dụ

try:
    a=5
    b=0
    print (a/b)
except TypeError as te:
    print(f'Unsupported operation: {te.args}')
except ZeroDivisionError as zde:
    print (f'Division by zero not allowed: {zde.args}')
print ('Out of try except blocks')

Hãy để ý phần as <biến> ở phần header của mỗi khối except. Với lỗi viết này, object exception sẽ được trỏ tới bởi tương ứng và bạn có thể sử dụng trong khối except.

Cuối cùng, bạn có thể xem cách sử dụng khối else và finally như sau:

Ví dụ

try:
    print("try block")
    x=int(input('Enter a number: '))
    y=int(input('Enter another number: '))
    z=x/y
except ZeroDivisionError:
    print("except ZeroDivisionError block")
    print("Division by 0 not accepted")
else:
    print("else block")
    print("Division = ", z)
finally:
    print("finally block")
    x=0
    y=0
print ("Out of try, except, else and finally blocks." )

Kết luận

Trong bài học này bạn đã làm quen với các loại lỗi và cơ chế xử lý ngoại lệ trong Python:

  • Python có hai loại lỗi chính là lỗi cú pháp và lỗi thực thi, trong đó lỗi thực thi còn được gọi với tên quen thuộc là ngoại lệ (exception).
  • Ứng với mỗi loại lỗi Python xây dựng một class riêng, gọi là các exception class.
  • Python cung cấp cơ chế xử lý ngoại lệ mặc định bằng cách dừng chương trình và thông báo lỗi và vị trí gây lỗi.
  • Nếu không muốn sử dụng cơ chế xử lý lỗi mặc định, bạn có thể tự xây dựng cách xử lý ngoại lệ riêng với cấu trúc try – except – else – finally.

Bài viết này đã giúp ích cho bạn?

Advertisements