Câu 1Câu nào sau đây là ví dụ về lỗi cú pháp?
Bắt ngoại lệ với try / except
Học cách bắt và xử lý ngoại lệ trong Python với try / except / finally để code của bạn không sập khi gặp lỗi runtime.
try / except là cơ chế bắt các lỗi phát sinh khi chạy và giữ chương trình tiếp tục chạy thay vì để nó crash. Dùng nó cho các thao tác được dự đoán đôi khi sẽ thất bại — lấy dữ liệu từ bên ngoài, kiểm tra đầu vào người dùng, thao tác với file, v.v.
Lỗi cú pháp vs. lỗi runtime
Python có hai loại lỗi lớn. Lỗi cú pháp xảy ra khi code vi phạm ngữ pháp Python và bị bắt trước khi chương trình chạy. Lỗi runtime (ngoại lệ) đúng ngữ pháp nhưng xuất hiện khi chương trình đang chạy. Chỉ lỗi runtime mới có thể bị bắt bằng try / except.
Lỗi cú pháp bị bắt ngay khoảnh khắc Python đọc code, nên chương trình không bao giờ chạy. Lỗi runtime chỉ phát sinh khi thực thi thực sự đến dòng đó — đó là lý do try / except có thể chặn được chúng.
# Lỗi cú pháp: thiếu dấu hai chấm. Fail trước khi dòng chạy
# if x > 0
# print("dương")
# Lỗi runtime: đúng ngữ pháp, nhưng fail khi chạy
x = 100
print(x / 0) # ZeroDivisionError: division by zero
print("dòng này không bao giờ chạy")
Khi một lỗi runtime phát sinh, mọi thứ sau dòng đó bị bỏ qua. Chạy đoạn kế tiếp và bạn sẽ thấy print() cuối không chạy — chương trình dừng tại IndexError.
# Xác nhận lỗi runtime: không có try, chương trình dừng lại
numbers = [10, 20, 30]
print(numbers[10]) # IndexError: list index out of range
print("dòng này không bao giờ chạy")
# Kết quả:
# Traceback (most recent call last):
# File "...", line 3, in <module>
# IndexError: list index out of range
Cơ bản về try / except
Đặt code rủi ro trong khối try: và code phục hồi trong khối except ErrorClass:.
Ngay khoảnh khắc một lỗi phát sinh trong try, phần còn lại của thân try bị bỏ qua và điều khiển nhảy đến except tương ứng. Code sau except vẫn tiếp tục chạy ngay cả khi có lỗi.
Chạy thân try. Nếu không có ngoại lệ nào phát sinh, except bị bỏ qua và điều khiển chuyển đến phần còn lại của chương trình. Nếu có ngoại lệ phát sinh, phần còn lại của thân try bị bỏ qua, except khớp chạy, và sau đó điều khiển tiếp tục.
# Bọc trong try giữ chương trình chạy ngay cả khi có chia cho 0
a = 100
try:
result = a / 0
print(result) # không bao giờ chạy
except ZeroDivisionError:
print("Không thể chia cho 0") # điều khiển nhảy đến đây
print("sau khối try") # luôn chạy sau try / except
# Kết quả: Không thể chia cho 0
# sau khối try
Bắt đối tượng ngoại lệ với as e
Viết except ErrorClass as e: đặt chi tiết của ngoại lệ vào e. print(e) hiện thông điệp lỗi, và type(e) hiện lớp lỗi. Ghi log những thông tin này là điểm khởi đầu vững chắc để truy vết vấn đề sau này.
user_input = "abc" # đáng lẽ là một số, nhưng lại nhận được chuỗi
try:
value = int(user_input)
except ValueError as e:
print("Đầu vào không phải số")
print(f"Chi tiết: {e}")
print(f"Loại: {type(e).__name__}")
# Kết quả: Đầu vào không phải số
# Chi tiết: invalid literal for int() with base 10: 'abc'
# Loại: ValueError
Chỉ mình thông điệp không cho bạn biết chỗ nào lỗi phát sinh, nên trong khi debug tiêu chuẩn là dùng kèm với import traceback. traceback.format_exc() trả về đường dẫn gọi (stack trace) tại khoảnh khắc ngoại lệ phát sinh, và ghi log nó làm cho phân tích nguyên nhân dễ hơn nhiều.
import traceback
try:
value = int("abc")
except ValueError as e:
print(f"Chi tiết: {e}")
print(f"Loại: {type(e).__name__}")
print("--- traceback ---")
print(traceback.format_exc())
# Kết quả:
# Chi tiết: invalid literal for int() with base 10: 'abc'
# Loại: ValueError
# --- traceback ---
# Traceback (most recent call last):
# File "...", line 4, in <module>
# value = int("abc")
# ValueError: invalid literal for int() with base 10: 'abc'
Nhiều khối except và Exception bao trùm
Bạn có thể xếp nhiều khối except trên một try. Cái khớp đầu tiên sẽ chạy, nên cách viết chuẩn là liệt kê chúng từ cụ thể nhất đến chung chung nhất.
Mọi lỗi runtime đều kế thừa từ Exception, nên except Exception as e: bắt bất cứ thứ gì, bao gồm cả các lỗi không dự đoán được.
numbers = [10, 20, 30]
index = 1
divisor = 0
try:
value = numbers[index]
print(value / divisor)
except IndexError as e:
print(f"Index ngoài phạm vi: {e}")
except ZeroDivisionError as e:
print(f"Không thể chia cho 0: {e}")
except Exception as e:
print(f"Lỗi khác: {type(e).__name__}: {e}")
# Kết quả: Không thể chia cho 0: division by zero
Đừng đặt Exception lên đầu
Exception là cha của hầu hết mọi ngoại lệ, nên đặt nó ở trên cùng sẽ ngăn bất kỳ except cụ thể hơn nào sau đó chạy. Dùng nó làm lưới an toàn cuối cùng cho các lỗi bất ngờ.
Chạy dọn dẹp với finally
Khối finally: là khe dọn dẹp luôn luôn chạy, dù có ngoại lệ phát sinh hay không. Dùng nó để giải phóng tài nguyên phải được đóng — file, kết nối DB, v.v.
Khối else: chạy chỉ khi không có ngoại lệ nào phát sinh. Nó không được dùng thường xuyên, nhưng làm rõ đường "chỉ khi thành công, đi tiếp".
Sau khi try chạy: không ngoại lệ → đến else, có ngoại lệ → đến except khớp. Dù sao đi nữa, finally luôn chạy ở cuối — đó là điểm mấu chốt.
# Đường happy: else và finally chạy
a, b = 10, 2
try:
result = a / b
except ZeroDivisionError:
print("Đã bắt chia cho 0")
else:
print(f"Thành công: {result}")
finally:
print("Đang chạy dọn dẹp")
# Kết quả: Thành công: 5.0
# Đang chạy dọn dẹp
print("---")
# Đường lỗi: except và finally chạy
a, b = 10, 0
try:
result = a / b
except ZeroDivisionError:
print("Đã bắt chia cho 0")
else:
print(f"Thành công: {result}")
finally:
print("Đang chạy dọn dẹp")
# Kết quả: Đã bắt chia cho 0
# Đang chạy dọn dẹp
Trong bài này bạn đã học những điều cơ bản về xử lý ngoại lệ với try / except, cách bắt thông tin ngoại lệ với as e, cách xếp nhiều khối except và dùng Exception làm bắt chung, và cách chạy dọn dẹp với finally. Tiếp theo: phát sinh ngoại lệ của riêng bạn với raise, và định nghĩa lớp ngoại lệ tùy chỉnh.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Đoạn code sau in gì?a = 100
try:
print(a / 0)
except ZeroDivisionError:
print("caught")
print("end")
Câu 3Trong try / except / else / finally, cái nào luôn chạy bất kể có ngoại lệ phát sinh hay không?