Học bằng cách đọc theo thứ tự

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 vs. lỗi runtime
Lỗi PythonLỗi cú pháp(bắt trước khi chạy)Lỗi runtime(phát sinh khi chạy)Chương trình không chạytry không bắt đượcCó thể bắtbằng try / exceptthiếu dấu :chia cho 0

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.

Luồng hoạt động của try / except
chạy thân trycó lỗi phát sinh?không lỗi(chạy đến hết)lỗi phát sinh(bỏ qua phần còn lại)bỏ qua exceptchạy except khớptiếp tục phần còn lạiKhôngkhớp

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ảo vệ khỏi việc truy cập index ngoài phạm vi trong một list tồn kho.

① Định nghĩa stock = [12, 8, 3] (số lượng trên mỗi kệ).

② Đầu tiên xác nhận điều gì xảy ra nếu bạn gọi stock[5] mà không có try. Đáp án giữ dòng này dưới dạng comment; bỏ comment để thấy chương trình dừng với IndexError.

③ Bây giờ chạy print(stock[5]) bên trong try và in "Kệ đó không tồn tại" từ except IndexError:.

④ Sau try / except, in "Tiếp tục…" để xác nhận rằng việc thực thi tiếp tục ngay cả sau ngoại lệ.

(Phần giải thích sẽ hiện ra khi bạn chạy code đúng.)

Python Editor

Chạy code để xem đầu ra

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
Những gì bạn có thể lấy ra từ đối tượng ngoại lệ
lỗi phát sinhint("abc")except ValueError as eđối tượng ngoại lệ estr(e)→ thông điệptype(e).__name__→ tên lớptraceback.format_exc()→ stack tracegán

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'

Khi đầu vào người dùng không thể chuyển sang số, in thông điệp, loại, và stack trace cùng nhau.

① Ở đầu, viết import traceback và đặt user_input = "Tokyo".

② Trong try, gọi int(user_input), và bắt nó bằng except ValueError as e:.

③ Bên trong except, in "Thông điệp: {e} / Loại: {type(e).__name__}" trên một dòng, rồi print(traceback.format_exc()).

Python Editor

Chạy code để xem đầu ra

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.

Sắp xếp except từ cụ thể đến chung chung
lỗi trong thân tryexceptIndexError?chạy handlerIndexErrorexceptZeroDivisionError?chạy handlerZeroDivisionErrorexceptException?bắt chunglỗi bất ngờtiếp tụcsau trykhớpkhông khớpkhớpkhông khớpkhớp
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ờ.

Lấy một số món từ lịch sử mua hàng của người dùng, cộng chúng lại, và tính trung bình.

① Định nghĩa history = [1200, 800, 1500] (số tiền mua) và take = 10 (số lượng lấy).

② Bên trong try, tính total = sum(history[:take]), rồi avg = total / len(history[:take])print(f"Trung bình: {avg}").

③ Thêm bắt chung: except ZeroDivisionError as e: in "Không có dữ liệu", và except Exception as e: in "Bất ngờ: {e}". (Trên đường happy, trung bình in bình thường.)

Python Editor

Chạy code để xem đầu ra

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".

Thứ tự thực thi của try / except / else / finally
chạy thân trycó ngoại lệ phát sinh?khối else(chỉ khi thành công)khối except(xử lý nó)khối finally(luôn chạy)tiếp tục phần còn lạiKhông (thành công)Có (thất bại)

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

Mô phỏng một lời gọi API và ghi log thành công, thất bại, và dọn dẹp trên mọi đường.

① Định nghĩa user_ids = [101, 102, 103]target = 0.

② Trong try, lấy user = user_ids[target] (chưa print).

③ Trong except IndexError:, in "Không có người dùng đó".

④ Trong else:, in f"Đã lấy: {user}" để nó chạy chỉ khi không có ngoại lệ.

⑤ Trong finally:, in "Đã đóng kết nối" để nó chạy trên cả hai đường.

Python Editor

Chạy code để xem đầu ra

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.

QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Câu nào sau đây là ví dụ về lỗi cú pháp?

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?