← Lập trình Python cơ bản

Bài 9 · Vận dụng · 20 phút· Cập nhật 11/06/2026

Lỗi & ngoại lệ trong Python

Biên soạn bởi Nguyễn Anh Tuấn

Xử lý lỗi & ngoại lệ (exception) trong Python: try/except/finally, raise, đọc traceback, bắt lỗi cụ thể và triết lý EAFP.

Khi “có chuyện” lúc chạy, Python tạo một object lỗi và “ném” (raise) nó. Vài loại hay gặp: ValueError (giá trị sai), TypeError (sai kiểu), KeyError (key không có), ZeroDivisionError, IndexError, FileNotFoundError… Không bắt thì chương trình dừng và in traceback:

loi.py

int("abc")

Kết quả khi chạy

Traceback (most recent call last):
  File "loi.py", line 1, in <module>
    int("abc")
ValueError: invalid literal for int() with base 10: 'abc'
  • Đọc traceback TỪ DƯỚI LÊN: dòng cuối = loại lỗi + thông điệp.
  • Mỗi loại lỗi là một kiểu riêng (ValueError, KeyError…), đều thuộc Exception.
  • Lỗi không bắt sẽ dừng chương trình - nên ta cần cách XỬ LÝ chúng.

Đặt phần “có thể lỗi” trong try; nếu lỗi xảy ra và khớp một except, Python nhảy tới đó. finally LUÔN chạy. Bấm Tiếp → với ba tình huống để thấy luồng nhảy:

1 def chia(a, b):
2 try:
3 kq = a / b
4 print("Ket qua:", kq)
5 except ZeroDivisionError:
6 print("Khong chia cho 0!")
7 finally:
8 print("Xong.")
chạy
Màn hình
(chưa in gì)
bước 1/6
Vào khối try.
  • try lỗi ở đâu thì DỪNG ngay, nhảy tới except khớp loại lỗi.
  • except không khớp → lỗi LAN RA ngoài (nhưng finally vẫn chạy trước).
  • finally luôn chạy - hợp để dọn dẹp (đóng tệp, kết nối).

Khi hàm của bạn gặp dữ liệu không hợp lệ, hãy raise một lỗi rõ nghĩa thay vì âm thầm trả sai:

raise.py

def dat_tuoi(t):
    if t < 0:
        raise ValueError("tuoi khong the am")
    return t

try:
    dat_tuoi(-5)
except ValueError as e:
    print("Loi:", e)

Kết quả khi chạy

Loi: tuoi khong the am
  • raise LoaiLoi("thông điệp") để báo điều kiện sai cho người gọi.
  • except ... as e: bắt object lỗi, e chứa thông điệp.
  • Báo lỗi sớm & rõ tốt hơn để bug lan đi âm thầm.

Dạng đầy đủ còn có else (chạy khi KHÔNG có lỗi), và bạn nên bắt đúng loại lỗi - đừng nuốt tất cả:

full.py

def doc_so(s):
    try:
        n = int(s)          # co the loi
    except ValueError:
        return None         # bat CU THE loai nay
    else:
        return n * 2        # chi chay khi try KHONG loi
    finally:
        print("xong")       # LUON chay

print(doc_so("5"))   # -> xong, roi 10
print(doc_so("x"))   # -> xong, roi None

Kết quả khi chạy

xong
10
xong
None

bắt cụ thể: SAI → ĐÚNG

# SAI: nuot MOI loi (ke ca go sai ten bien -> bug an)
try:
    kq = tinh()
except:
    kq = 0

# DUNG: bat dung loai ban biet xu ly
try:
    kq = tinh()
except (ValueError, ZeroDivisionError):   # mot tuple nhieu loai
    kq = 0
  • else: chạy khi try KHÔNG lỗi (tách phần “lỡ lỗi” khỏi phần “chạy tiếp khi ổn”).
  • except (A, B): bắt nhiều loại trong một nhánh; nhiều except cho xử lý khác nhau.
  • ĐỪNG dùng except: trống - nó nuốt cả lỗi bạn không ngờ (NameError…) → bug ẩn.

Bạn tạo được loại lỗi riêng bằng cách kế thừa Exception (hoặc một loại cụ thể) - giúp người gọi bắt đúng tình huống nghiệp vụ:

custom.py

class SoDuKhongDu(ValueError):      # loi RIENG, ke thua ValueError
    pass

def rut(sodu, tien):
    if tien > sodu:
        raise SoDuKhongDu(f"thieu {tien - sodu}")
    return sodu - tien

try:
    rut(100, 150)
except SoDuKhongDu as e:            # bat dung loai nghiep vu
    print("Loi:", e)

Kết quả khi chạy

Loi: thieu 50

“raise ... from e”: giữ nguyên nhân gốc

Khi bắt một lỗi rồi ném lỗi khác, dùng raise LoiMoi(...) from e để GIỮ lỗi gốc trong traceback (“The above exception was the direct cause of…”). Nhờ vậy bạn vừa báo lỗi rõ nghĩa cho người gọi, vừa không mất manh mối gỡ lỗi.

Có hai lối nghĩ. LBYL (“nhìn trước khi nhảy”): kiểm tra trước rồi mới làm. EAFP (“xin lỗi dễ hơn xin phép”): cứ thử, sai thì bắt. Python ưa EAFP - thường gọn và an toàn hơn:

eafp.py

diem = {"Lan": 9}

# LBYL: kiem tra truoc
if "Nam" in diem:
    print(diem["Nam"])
else:
    print("chua co Nam")

# EAFP: cu thu, sai thi bat (Pythonic)
try:
    print(diem["Nam"])
except KeyError:
    print("chua co Nam")

Kết quả khi chạy

chua co Nam
chua co Nam

Vì sao Python ưa EAFP

Cùng kết quả, nhưng EAFP tránh “khoảng trống” giữa lúc KIỂM và lúc LÀM (giá trị có thể đổi giữa chừng), và gọn hơn khi có nhiều bước. Với dict, .get(key, mặc_định) còn ngắn hơn nữa. Quy tắc: bắt cụ thể loại lỗi bạn biết xử lý - đừng dùng except: trống nuốt mọi thứ.

Câu hỏi thường gặp

Là một sự kiện “có chuyện” lúc chạy - chia cho 0, key không có, ép kiểu hỏng… Python tạo một OBJECT lỗi (vd ZeroDivisionError) và “ném” (raise) nó. Nếu không ai bắt, chương trình dừng và in TRACEBACK.

Đọc TỪ DƯỚI LÊN: dòng cuối cho biết LOẠI lỗi và thông điệp (vd ValueError: invalid literal...). Các dòng trên cho biết lỗi xảy ra ở tệp/dòng nào và chuỗi lời gọi dẫn tới đó.

finally LUÔN chạy - dù try thành công, bị bắt, hay lỗi văng ra ngoài. Dùng để DỌN DẸP chắc chắn: đóng tệp, đóng kết nối, giải phóng tài nguyên. (Với tệp, câu lệnh with còn gọn hơn - học ở khoá nâng cao.)

Bài “Làm việc với hệ thống tệp” - khoá Python nâng cao →

KHÔNG. except: hay except Exception: bắt MỌI lỗi sẽ “nuốt” cả lỗi bạn không ngờ (gõ sai tên biến, hết bộ nhớ…), khiến bug ẩn đi. Hãy bắt CỤ THỂ loại lỗi bạn biết cách xử lý (except KeyError, except ValueError…).

Khi hàm của bạn gặp dữ liệu/điều kiện không hợp lệ và muốn báo cho người gọi. raise ValueError("tuoi khong the am") ném một lỗi rõ nghĩa, tốt hơn là trả None âm thầm rồi để bug lan đi.

EAFP = “Easier to Ask Forgiveness than Permission”: cứ THỬ làm, sai thì bắt (try/except). LBYL = “Look Before You Leap”: KIỂM TRA trước rồi mới làm (if ... in ...). Python ưa EAFP vì gọn và tránh “khoảng trống” giữa lúc kiểm và lúc làm (giá trị có thể đổi).

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Đọc một traceback thì nên đọc theo hướng nào để biết loại lỗi?

  1. 1

    Đọc traceback

    Chạy int("abc"). Loại lỗi gì, thông điệp ra sao (dòng cuối traceback)?

    Hoàn thành khi: ValueError: invalid literal for int() with base 10: 'abc'.

  2. 2

    Bắt lỗi chia 0

    Viết hàm chia(a, b) dùng try/except trả "Khong chia cho 0" khi b = 0.

    Hoàn thành khi: try: return a / b; except ZeroDivisionError: return "Khong chia cho 0".

  3. 3

    finally luôn chạy

    Dùng công cụ Bước 2: với chia(10, "x"), finally có chạy không trước khi lỗi văng ra?

    Hoàn thành khi: Có - "Xong." in ra trước, rồi TypeError mới lan ra ngoài.

  4. 4

    Tự ném lỗi

    Viết dat_tuoi(t): nếu t < 0 thì raise ValueError("tuoi khong the am").

    Hoàn thành khi: raise ValueError(...) khi t < 0; ngược lại return t.

  5. 5

    EAFP

    Cho diem = {"Lan": 9}. Lấy điểm "Nam" theo kiểu EAFP, in "chua co" nếu không có.

    Hoàn thành khi: try: print(diem["Nam"]); except KeyError: print("chua co"). (Hoặc diem.get("Nam", "chua co").)

  6. 6

    Bắt cụ thể

    Vì sao except: (trống) là thói quen xấu? Nêu một bug nó có thể che giấu.

    Hoàn thành khi: Nó nuốt mọi lỗi - kể cả gõ sai tên biến (NameError) - làm bug ẩn, khó tìm.