Lỗi hoặc sai sót trong một chương trình được gọi chung là lỗi. Chúng hầu như là lỗi do lập trình viên gây ra. Quá trình tìm kiếm và loại bỏ lỗi gọi là gỡ lỗi (debugging). Lỗi có thể chia thành 3 nhóm chính:
- Lỗi cú pháp
- Lỗi thực thi
- Lỗi lôgic.
Lỗi cú pháp
Python sẽ tìm thấy những loại lỗi này khi nó cố gắng phân tích cú pháp chương trình của bạn và thoát ra với một thông báo lỗi mà không chạy bất chứ thứ gì. Lỗi cú pháp là những lỗi trong việc sử dụng ngôn ngữ Python, tương tự như lỗi chính tả hoặc ngữ pháp trong một ngôn ngữ như tiếng Anh, ví dụ câu would you some tea? không có nghĩa vì nó thiếu động từ.
Các lỗi cú pháp Python phổ biến bao gồm:
- bỏ một từ khóa
- đặt một từ khóa sai chỗ
- bỏ đi một ký hiệu, chẳng hạn dấu hai chấm, dấu phảy hoặc dấu ngoặc
- viết sai chính tả một từ khóa
- thụt lề sai
- khối trống
Bạn không thể để trống hoàn toàn một khối, chẳng hạn như phần thân của
if
hoặc của một hàm. Nếu muốn tạo một khối không làm gì cả, bạn có thể sử dụngpass
bên trong khối.
Python sẽ cố gắng hết sức để cho bạn biết lỗi nằm ở đâu, nhưng đôi khi thông báo của nó có thể gây hiểu nhầm. Ví dụ như bạn quên thoát dấu ngoặc kép bên trong chuỗi, bạn có thể gặp lỗi cú pháp liên quan đến một vị trí phía sau trong mã, mặc dù nó không phải là nguồn gốc thực sự của vấn đề. Nếu bạn không thể thấy bất kỳ điều gì sai trên dòng được chỉ định trong thông báo lỗi, hãy thử quay lại vài dòng trước đó. Khi bạn lập trình nhiều hơn, bạn sẽ xác định và sửa lỗi tốt hơn.
Dưới đây là một số ví dụ về lỗi cú pháp trong Python:
myfunction(x, y):
return x + y
else:
print("Hello!")
if mark >= 50:
print("You passed!")
if arriving:
print("Hi!")
else:
print("Bye!")
if flag:
print("Flag is set!")
Lỗi thực thi
Nếu một chương trình đúng về mặt ngữ pháp – tức là không có lỗi cú pháp – thì nó sẽ được chạy bởi trình thông dịch Python. Tuy nhiên, chương trình có thể thoát đột ngột khi thực thi nếu nó gặp lỗi thực thi – một vấn đề không được phát hiện khi chương trình được phân tích cú pháp, nhưng chỉ tiết lộ khi một dòng cụ thể được thực thi. Khi một chương trình bị tạm dừng vì lỗi thực thi, chúng ta nói rằng nó đã bị lỗi.
Một số ví dụ về lỗi thực thi trong Python:
- chia cho số không
- thực hiện phép toán trên dữ liệu không tương thích
- sự dụng một định danh chưa được định nghĩa
- truy cập phần tử của danh sách, khóa của từ điển hoặc thuộc tính của đối tượng không tồn tại.
- cố gắng truy cập một file không tồn tại.
Lỗi thực thi thường xảy ra nếu bạn không xem xét tất cả các giá trị mà một biến có thể chứa, đặc biệt khi bạn xử lý dữ liệu nhập của người dùng. Bạn nên luôn cố gắng thêm các kiểm tra vào mã của mình để đảm bảo rằng nó có thử xử lý các trường hợp đầu vào xấu một cách duyên dáng. Chúng ta sẽ xem xét vấn đề chi tiết hơn phần xử lý ngoại lệ.
Lỗi lôgic
Lỗi lôgic là khó sửa nhất. Chúng xảy ra khi chương trình chạy mà không bị treo, nhưng tạo ra kết quả không chính xác. Lỗi là do sai sót của thuật toán chương trình. Bạn sẽ không nhận được thông báo lỗi vì không có lỗi cú pháp hoặc lỗi thực thi (ngoại lệ) nào xảy ra. Bạn sẽ phải tự mình tìm ra vấn đề bằng cách xem xét tất cả các phần có liên quan của mã.
Xử lý ngoại lệ
Nếu chúng ta biết rằng một phần cụ thể trong chương trình của chúng ta có khả năng gây ra lỗi thực thi, chúng ta có thể cho Python biết phải làm gì nếu điều đó xảy ra. Thay vì để lỗi làm hỏng chương trình của chúng ta, chúng ta có thể chặn nó, hãy làm gì đó đối với nó và cho phép chương trình tiếp tục chạy.
Tất các lỗi thực thi mà chúng ta gặp phải được gọi là ngoại lệ. Python có những đối tượng biểu diễn các loại ngoại lệ xảy ra trong trương chính, chúng là những đối tượng của của các lớp con của lớp Exception
.
Câu lệnh try
và except
Để xử lý các ngoại lệ, chúng ta sử dụng khối try-except:
try:
age = int(input("Please enter your age: "))
print("I see that you are %d years old." % age)
except ValueError:
print("Hey, that wasn't a number!")
Python sẽ chạy các lệnh trong khối try
. Nếu có ngoại lệ thuộc lớp ValueError
xảy ra tại bất cứ thời điểm nào khi nó đang thực thi, luồng điều khiển sẽ ngay lập tức chuyển đến khối except
và các lệnh còn lại trong khối try
bị bỏ qua.
Trong ví dụ này, chúng ta biết rằng có lỗi có thể xảy ra khi cố gắng chuyển đổi đầu vào của người dùng thành một số nguyên. Nếu chuỗi đầu vào không phải là một số, dòng này sẽ kích hoạt một lỗi ValueError
– đó là lý do tại sao chúng ta chỉ định nó là loại lỗi mà chúng ta sẽ xử lý.
Chúng ta có thể chỉ định một kiểu ngoại lệ chung chung hơn – hoặc thậm chí chỉ định tất cả ngoại lệ, điều này khiến except
khớp với bất kỳ ngoại lệ nào – nhưng đó là sẽ một ý tưởng tồi. Điều gì sẽ xảy ra nếu chúng ta gặp một lỗi hoàn toàn khác mà chúng ta không dự đoán trước? Nó cũng sẽ được xử lý và chúng ta không nhận thấy bất cứ điều gì bất thường đang xảy ra. Chúng ta muốn phản ứng theo những cách khác nhau đối với các loại lỗi khác nhau. Chúng ta nên luôn thử chọn các loại lỗi cụ thể thay vì chung chung cho các mệnh đề except
của mình.
Một mệnh đề except
có thể xử lý được nhiều loại lỗi: chúng ta có thể cung cấp nhiều loại ngoại lệ thay vì một loại:
try:
dividend = int(input("Please enter the dividend: "))
divisor = int(input("Please enter the divisor: "))
print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except (ValueError, ZeroDivisionError):
print("Oops, something went wrong!")
Chú ý rằng trong ví dụ trên nếu một ngoại lệ ValueError
xảy ra, chúng ta sẽ không biết liệu đó là do số bị chia hay do số chia không phải số nguyên – một trong hai dòng nhập có thể gây ra lỗi đó. Nếu chúng ta muốn cung cấp cho người dùng phản hồi cụ thể hơn về đầu vào nào là sai, chúng ta sẽ phải bọc mỗi dòng đầu vào trong một try-except
riêng biệt:
try:
dividend = int(input("Please enter the dividend: "))
except ValueError:
print("The dividend has to be a number!")
try:
divisor = int(input("Please enter the divisor"))
except ValueError:
print("The divisor has to be a number!")
try:
print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except ZeroDivisionError:
print("The dividend may not be zero!")
Nói chung, bạn nên sử dụng các lệnh try-except
để bảo vệ các khối mã nhỏ chống lại các lỗi cụ thể hơn là bọc các khối mã lớn và viết mã khôi phục lỗi một cách chung chung, mơ hồ. Đôi khi có vẻ không hiệu quả và dài dòng khi viết nhiều câu lệnh try-except
nhỏ thay vì một lệnh try-except
duy nhất, nhưng chúng ta có thể giảm thiểu điều này ở mức độ nào đó bằng cách sử dụng hiệu quả các vòng lặp và hàm để giảm lượng trùng lặp mã.
Mệnh đề else
và finally
Có hai mệnh đề khác mà bạn có thể thêm vào khối try-except
: else
và finally
. else
sẽ chỉ được thực thi nếu try
không đưa ra ngoại lệ.
try:
age = int(input("Please enter your age: "))
except ValueError:
print("Hey, that wasn't a number!")
else:
print("I see that you are %d years old." % age)
Mệnh đề finally
luôn được chạy ở cuối try-except
bất kể không có ngoại lệ hoặc có ngoại lệ, ngoại lệ được xử lý hay không được xử lý, khối được thoát ra bởi break
, continue
hoặc return
. Chúng ta có thể sử dụng mệnh đề finally
cho mã dọn dẹp mà chúng ta muốn luôn được thực thi.
try:
age = int(input("Please enter your age: "))
except ValueError:
print("Hey, that wasn't a numer!")
else:
print("I see that you are %d years old." % age)
finally:
print("It was really nice talking to you. Goodbye!")
Lệnh with
Sử dụng đối tượng ngoại lệ
Các đối tượng ngoại lệ của Python chứa nhiều thông tin hơn là chỉ loại lỗi. Chúng cũng đi kèm với một số loại thông báo – chúng ta đã thấy một số thông báo này được hiển thị khi chương trình của chúng ta gặp sự cố. Thường thì những thông báo này không thân thiện với người dùng lắm – nếu chúng ta muốn thông báo lỗi cho người dùng, chúng ta thường cần viết một thông báo mô tả hơn là giải thích lỗi liên quan đến những gì người dùng đã làm. Ví dụ: nếu lỗi do nhập sai, sẽ hữu ích nếu cho người dùng biết giá trị đầu vào nào không chính xác.
Đôi khi thông báo ngoại lệ chứa thông tin hữu ích mà chúng ta muốn hiển thị cho người dùng. Để truy cập thông báo, chúng ta cần truy cập được vào đối tượng ngoại lệ. Chúng ta có thể gán đối tượng cho một biến mà chúng ta có thể sử dụng trong khối except
như sau:
try:
age = int(input("Please enter your age: "))
except ValueError as err:
print(err)
err
không phải là chuỗi, nhưng Python biết phải chuyển đổi nó thành chuỗi như thế nào – chuỗi này biểu diễn thông báo của ngoại lệ, chính xác là cái chúng ta muốn. Chúng ta cũng có thể kết hợp thông báo của ngoại lệ với thông báo riêng:
try:
age = int(input("Please enter your age: "))
except ValueError as err:
print("You entered incorrect age input: %s" % err)
Tạo một ngoại lệ
Chúng ta có thể tự tạo một ngoại lệ bằng lệnh raise
.
try:
age = int(input("Please enter your age: "))
if age < 0:
raise ValueError("%d is not a valid age. Age must e positive or zero.")
except ValueError as err:
print("You entered incorrect age input: %s" % err)
else:
print("I see that you are %d years old." % age)
Đây là một số kiểu lỗi mà chúng ta có thể tạo ra trong mã:
TypeError
: lỗi này cho biết rằng một biến có kiểu sai cho một phép toán. Chúng ta có thể tạo nó nếu một tham số không thuộc loại mà chúng ta biết cách xử lý.ValueError
: lỗi này được sử dụng để chỉ ra rằng một biến có kiểu đúng nhưng giá trị sai. Ví dụ, chúng ta đã sử dụng nó khiage
là một số nguyên, nhưng không đúng là số dương.NotImplementedError
: chỉ ra rằng phương thức của một lớp phải được triển khai trong một lớp con.