Lỗi là cách Python cho bạn biết chuyện gì đã xảy ra
Mỗi lỗi trong Python là một đối tượng có loại, thông báo và traceback, tức là chuỗi lời gọi dẫn tới lỗi. Đọc lỗi cho thạo là kỹ năng gỡ lỗi (debug) quan trọng nhất bạn có thể học. Trang này đi qua những lỗi bạn thật sự sẽ gặp, cùng những thói quen giúp sửa chúng nhanh.
Để tìm hiểu sâu hơn về cơ chế try/except và cách tự ném ngoại lệ, trang Ngoại lệ trình bày cú pháp. Trang này nói về từng lỗi cụ thể và cách đọc chúng.
Đọc traceback
Chạy một đoạn code bị hỏng:
def divide(a, b):
return a / b
def report(values):
for v in values:
print(divide(10, v))
report([5, 2, 0])
Python in ra thứ gì đó như sau:
Traceback (most recent call last):
File "script.py", line 8, in <module>
report([5, 2, 0])
File "script.py", line 6, in report
print(divide(10, v))
File "script.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
Hãy đọc từ dưới lên:
ZeroDivisionError: division by zero: loại ngoại lệ và thông báo. Đây là cái gì đã hỏng.return a / btrongdivide, dòng 2: dòng thật sự ném ra lỗi.print(divide(10, v))trongreport, dòng 6: lời gọi kích hoạt lỗi.report([5, 2, 0])ở cấp module, dòng 8: nơi mọi thứ bắt đầu.
Khung dưới cùng gần như luôn là chỗ cần sửa. Khi một thư viện ném lỗi sâu bên trong code của nó, hãy đi ngược lên traceback tới khung đầu tiên nằm trong code của bạn: đó là lời gọi mà bạn đã truyền đầu vào sai.
Những lỗi bạn sẽ gặp nhiều nhất
NameError
"Name 'mesage' is not defined." Do gõ nhầm hoặc dùng một biến trước khi gán giá trị. Ở các phiên bản gần đây, thông báo lỗi của Python thường gợi ý một tên có thể đúng ("Did you mean 'message'?").
Cách sửa: kiểm tra chính tả và phạm vi. Nếu tên đó chỉ tồn tại bên trong một hàm, bạn không thể đọc nó từ bên ngoài.
TypeError
"Can only concatenate str (not 'int') to str." Sai kiểu cho phép toán. Những trường hợp kinh điển: cộng chuỗi với số, gọi một thứ không gọi được, truyền sai số lượng đối số cho hàm.
Cách sửa: chuyển kiểu tường minh (str(30), int("30")) hoặc xem lại thứ bạn thật sự đang truyền vào. Một f-string thường dễ đọc hơn việc nối bằng + giữa các kiểu khác nhau: f"age: {30}".
ValueError
"Invalid literal for int() with base 10: 'hello'." Kiểu thì đúng (int() nhận chuỗi), nhưng giá trị không phù hợp. Hay gặp với int(), float(), phân tích ngày tháng và những hàm nhận đối số có giới hạn.
Cách sửa: kiểm tra trước khi chuyển đổi, hoặc bắt lỗi và xử lý:
KeyError
"KeyError: 'charlie'." Khóa không có trong dict. Ba cách sửa đúng chất Python, chọn theo ý định:
Với một dict mà khóa còn thiếu nên được tự khởi tạo, collections.defaultdict rất đáng để tìm hiểu.
IndexError
"List index out of range." Bạn hỏi một vị trí mà dãy không có.
Cách sửa: kiểm tra độ dài trước, dùng -1 để lấy phần tử cuối, hoặc dùng cắt lát (numbers[5:6] trả về [] thay vì báo lỗi).
AttributeError
"'NoneType' object has no attribute 'upper'." Bạn gọi một phương thức trên thứ không có phương thức đó. Gần như luôn có nghĩa là một biến mang kiểu không như mong đợi, thường là None trong khi bạn chờ một giá trị thật.
Cách sửa: tìm xem None từ đâu ra. print(type(var)) hoặc một breakpoint ngay trước dòng lỗi là cách nhanh nhất. Những hàm "đôi khi thất bại" thường trả về None; hãy kiểm tra giá trị trả về trước khi gọi phương thức trên nó.
ModuleNotFoundError (và ImportError)
import fastapi
"No module named 'fastapi'." Package chưa được cài vào trình thông dịch Python mà script của bạn đang dùng. Hai nguyên nhân phổ biến:
- Bạn thật sự chưa cài nó. Hãy chạy
python -m pip install fastapitrong đúng môi trường. - Bạn đã cài, nhưng vào một bản Python khác. Chuyện này xảy ra liên tục trên macOS khi
pipvàpythontrỏ tới hai bản cài khác nhau.
Cách sửa chắc chắn là cài bằng chính trình thông dịch mà bạn dùng để chạy:
python -m pip install fastapi
Nếu bạn dùng môi trường ảo (và bạn nên dùng), hãy chắc là nó đã được kích hoạt trước cả pip install lẫn python script.py.
FileNotFoundError
with open("settings.yaml") as f:
config = f.read()
"[Errno 2] No such file or directory: 'settings.yaml'." Đường dẫn không tồn tại so với nơi script đang chạy.
Cách sửa: in os.getcwd() ở đầu script để xác nhận Python đang tìm ở đâu. Dùng đường dẫn tuyệt đối hoặc pathlib.Path(__file__).parent / "settings.yaml" để neo đường dẫn vào chính vị trí của script.
EOFError
name = input("Name: ")
"EOF when reading a line." input() cố đọc từ stdin và không nhận được gì: hoặc đầu vào được đưa qua pipe từ một nguồn rỗng, hoặc bạn đã nhấn Ctrl-D ở dấu nhắc.
Cách sửa: nếu việc dùng pipe là hợp lệ, hãy bọc lời gọi lại:
try:
name = input("Name: ")
except EOFError:
name = "anonymous"
Trong trình soạn thảo trên trình duyệt của các trang tài liệu này, môi trường chạy giả lập đầu vào; bạn sẽ không gặp lỗi này ở đây.
IndentationError và SyntaxError
IndentationError: expected an indented block after function definition on line 2
Hai lỗi này đặc biệt: chúng xảy ra trước khi chương trình chạy. Python từ chối phân tích tệp.
IndentationError: phần thân củadef,if,for... bị thiếu hoặc thụt lề lệch. Phần lớn trình soạn thảo hiển thị thụt lề; hãy bật "show whitespace" để phát hiện tab và dấu cách bị trộn lẫn.SyntaxError: bạn quên dấu hai chấm, đặt ngoặc không khớp hoặc viết sai một từ khóa. Các phiên bản Python gần đây đặt một mũi tên (^) chỉ vào ký tự gây lỗi.
Cách sửa: thông báo lỗi nêu rõ dòng nào. Hãy tới xem. Nếu dòng đó không có gì sai rõ ràng, hãy kiểm tra dòng phía trên: một dấu ngoặc chưa đóng ở vài dòng trước thường hiện ra thành lỗi cú pháp ở xa phía dưới.
RuntimeError
Lỗi gom chung cho "có gì đó hỏng lúc chạy mà không thuộc lỗi nào cụ thể hơn". RecursionError (vượt quá độ sâu đệ quy) là một dạng chuyên biệt hay gặp. Code thư viện thường ném RuntimeError khi nó rơi vào trạng thái không hợp lệ.
Cách sửa: đọc thông báo, thường nó mô tả khá rõ. Nếu nguyên nhân là đệ quy, hãy chuyển sang vòng lặp, hoặc trong vài trường hợp hiếm thì tăng sys.setrecursionlimit.
Gỡ lỗi bằng print
Trước khi dùng tới trình gỡ lỗi thật, print(), hay tốt hơn là dạng f"{var=}", đã bắt được phần lớn bug:
f"{var=}" in ra cả biểu thức lẫn giá trị của nó, nên bạn không phải gõ lại tên trong chuỗi định dạng. Chạy script, lướt qua đầu ra, tìm dòng mà một giá trị bắt đầu sai. Phần lớn những bug "bí ẩn" trở nên rõ ràng chỉ sau ba bốn lệnh print đặt đúng chỗ.
Hãy dọn các lệnh print trước khi commit. Với code sẽ đưa vào sử dụng, logging.debug(...) tốt hơn print: bạn có thể bật tắt log gỡ lỗi mà không phải sửa từng dòng.
breakpoint() và pdb
Khi print không đủ, breakpoint() đưa bạn vào trình gỡ lỗi tương tác của Python ngay tại điểm đó:
def discount(price, percent):
breakpoint()
return price * (1 - percent / 100)
discount(100, 20)
Hãy chạy script trong một terminal thật. Bạn sẽ dừng ở dấu nhắc (Pdb). Vài lệnh cần biết:
p variable: in một biến.n: chạy tới dòng tiếp theo.s: đi vào bên trong một lời gọi hàm.c: tiếp tục chạy tới breakpoint tiếp theo hoặc tới cuối.q: thoát.l: liệt kê code quanh dòng hiện tại.
Trình gỡ lỗi trong IDE (VS Code, PyCharm) bọc cùng giao thức đó trong giao diện đồ họa: breakpoint đặt ở lề, thanh bên hiển thị các biến. Hãy chọn cách nào bạn thấy thuận tay hơn.
Thói quen giúp bạn ít gặp lỗi hơn
- Đặt tên biến có ý nghĩa. Một nửa số
TypeErrorvàAttributeErrorbiến mất khi bạn không thể quên trong biến đang chứa gì. - Kiểm tra đầu vào ngay tại ranh giới. Phân tích và kiểm tra đầu vào của người dùng hoặc nội dung tệp một lần, ở đầu hàm. Phần code còn lại có thể tin vào các giá trị đó.
- Thất bại ồn ào, đừng thất bại lặng lẽ. Một
except Exception: passtrống trơn che mất đúng những lỗi bạn cần thấy. Hãy bắt những ngoại lệ cụ thể, xử lý chúng có chủ đích, và để phần còn lại lan ra ngoài. - Đọc phần cuối traceback trước. Mỗi phút bỏ ra để học đọc traceback sẽ được đền đáp gấp trăm lần.
Phần lớn lỗi không phải là bí ẩn: chúng là lỗi gõ nhầm một dòng hoặc nhầm kiểu, kèm thông báo rõ ràng. Hãy tin thông báo lỗi; thường nó nói đúng.
Bạn đã đi hết chặng đường
Đây là phần cuối của mục tham khảo. Bạn đã đi từ "Python là gì?" qua biến, luồng điều khiển, tập hợp, hàm, lớp, vòng lặp, dữ liệu thực tế và lỗi. Từ đây, các bước tiếp theo mang hình dạng dự án: chọn một thứ bạn muốn xây dựng, rồi lần ngược lại những phần cần học sâu hơn. Các trang này vẫn ở đây khi bạn quay lại với một câu hỏi cụ thể.
Câu hỏi thường gặp
KeyError trong Python là gì?
KeyError xảy ra khi bạn tra một khóa không tồn tại trong dict. users["missing"] ném ra KeyError: 'missing'. Các cách an toàn thay thế là users.get("missing", default), users.get("missing") (trả về None), hoặc kiểm tra tường minh bằng if key in users:.
EOFError trong Python là gì?
EOFError (end-of-file, hết tệp) xảy ra khi input() không đọc được gì vì luồng đầu vào đã đóng. Bạn sẽ gặp nó nhiều nhất khi một script dùng input() được chạy qua pipe mà không có dữ liệu, hoặc khi bạn nhấn Ctrl-D ở dấu nhắc tương tác. Hãy bọc bằng try/except EOFError nếu script phải xử lý đầu vào qua pipe.
ModuleNotFoundError là gì?
ModuleNotFoundError nghĩa là import X không tìm thấy module nào tên X. Hoặc package chưa được cài (pip install X), hoặc nó được cài vào một bản Python khác với bản đang chạy code của bạn (rất hay gặp khi máy có nhiều bản Python). python -m pip install X sửa trường hợp thứ hai bằng cách dùng đúng trình thông dịch đó.
Đọc traceback trong Python như thế nào?
Đọc từ dưới lên. Dòng cuối cùng là loại ngoại lệ và thông báo, tức là điều cụ thể đã hỏng. Các dòng phía trên cho thấy chuỗi lời gọi dẫn tới đó, và code của chính bạn thường nằm gần phía dưới. Hãy mở tệp và dòng ở khung thấp nhất thuộc về code của bạn; đó là chỗ cần sửa.