DHDataHọc
Lý thuyếtBài 29/306 phút đọc+10 XP

Lỗi thường gặp

Bạn sẽ học được

  • Đọc traceback để tìm đúng dòng gây lỗi.
  • Nhận diện 6 exception hay gặp nhất khi xử lý dữ liệu.
  • Bắt lỗi đúng cách: try/except/else/finally, bắt cụ thể, không nuốt lỗi.
  • Chủ động ném lỗi với raise và tạo exception riêng.
  • Hai phong cách kiểm tra: EAFP và LBYL.

Khái niệm cốt lõi

Python báo lỗi bằng exception. Khi một exception không được bắt, chương trình dừng và in traceback — danh sách lời gọi từ ngoài vào trong, dòng cuối cùng là nguyên nhân:

code
Traceback (most recent call last):
  File "bao_cao.py", line 12, in <module>
    print(tong[cua_hang])
KeyError: 'b'

Đọc traceback từ dưới lên: dòng cuối cho biết loại lỗi và thông điệp; ngay trên là dòng code gây lỗi; cao hơn là các lời gọi dẫn tới đó. Dòng cuối là thứ bạn cần trước tiên.

Exception cũng là một đối tượng có kiểu (class). Tên kiểu chính là phần trước dấu hai chấm: KeyError, ValueError, TypeError…

Sáu lỗi hay gặp nhất

Bảng dưới đây là sáu exception chiếm phần lớn lỗi khi mới xử lý dữ liệu:

ExceptionKhi nàoVí dụ thông điệp
KeyErrorTruy cập khóa không có trong dictKeyError: 'b'
IndexErrorChỉ số vượt độ dài listIndexError: list index out of range
ValueErrorĐúng kiểu nhưng sai giá trịValueError: invalid literal for int() with base 10: '12a'
TypeErrorSai kiểu dữ liệuTypeError: can only concatenate str (not "int") to str
AttributeErrorGọi phương thức không tồn tạiAttributeError: 'str' object has no attribute 'append'
FileNotFoundErrorMở file không tồn tạiFileNotFoundError: [Errno 2] No such file or directory: 'khong_ton_tai.csv'

Ví dụ từng lỗi:

code
tong = {"a": 100, "c": 250}
print(tong["b"])
code
KeyError: 'b'
code
rows = ["2026-01", "2026-02"]
print(rows[5])
code
IndexError: list index out of range
code
int("12a")
code
ValueError: invalid literal for int() with base 10: '12a'
code
"năm " + 2026
code
TypeError: can only concatenate str (not "int") to str
code
ten = "An"
ten.append("!")
code
AttributeError: 'str' object has no attribute 'append'
code
open("khong_ton_tai.csv")
code
FileNotFoundError: [Errno 2] No such file or directory: 'khong_ton_tai.csv'

Phân biệt quan trọng: ValueError là "đúng kiểu, sai nội dung" — int("12a") là chuỗi đúng kiểu nhưng không phải số. TypeError là "sai kiểu hoàn toàn" — cộng chuỗi với số, gọi .append trên chuỗi.

Bắt lỗi

Cú pháp đầy đủ gồm bốn khối:

code
def doc_so(raw):
    try:
        value = int(raw)
    except ValueError:
        print(f"'{raw}' không phải số nguyên")
        return None
    else:
        print("Đọc thành công")
        return value
    finally:
        print("Kết thúc lần đọc")

print(doc_so("42"))
print(doc_so("12a"))
code
Đọc thành công
Kết thúc lần đọc
42
'12a' không phải số nguyên
Kết thúc lần đọc
None

Thứ tự chạy:

  • try: đoạn có thể lỗi.
  • except: chỉ chạy khi có lỗi tương ứng.
  • else: chỉ chạy khi try không lỗi — đặt phần "việc tiếp theo" ở đây để không bắt nhầm lỗi của chính nó.
  • finally: luôn chạy, kể cả khi có lỗi hay có return — dùng để đóng file, giải phóng tài nguyên.

Bắt nhiều loại:

code
def to_int(raw):
    try:
        return int(raw.replace(",", ""))
    except (TypeError, ValueError):
        return 0

print(to_int("1,200"), to_int(None), to_int("abc"))
code
1200 0 0

Ba sai lầm khi bắt lỗi

1. Bắt quá rộng. except Exception: hoặc except: bắt mọi thứ, kể cả lỗi lập trình như gõ sai tên biến. Dùng except ValueError: — chỉ loại bạn thực sự xử lý được.

2. Nuốt lỗi im lặng. except ValueError: pass làm dữ liệu sai biến mất không dấu vết; báo cáo thiếu dòng mà không ai biết. Nên ghi log hoặc đếm số dòng lỗi:

code
loi = 0
for raw in ["10", "abc", "20"]:
    try:
        value = int(raw)
    except ValueError:
        loi += 1
print("Số dòng lỗi:", loi)
code
Số dòng lỗi: 1

3. Bọc cả khối lớn. Chỉ đặt trong try đúng dòng có thể lỗi. Bọc cả hàm 20 dòng khiến bạn không biết lỗi đến từ đâu và vô tình bắt lỗi của đoạn không liên quan.

Chủ động ném lỗi

Khi dữ liệu đầu vào không hợp lệ, ném lỗi rõ ràng tốt hơn để lỗi lan xuống chỗ khó hiểu:

code
def tinh_trung_binh(diem):
    if not diem:
        raise ValueError("Danh sách điểm rỗng")
    return sum(diem) / len(diem)

try:
    print(tinh_trung_binh([]))
except ValueError as e:
    print("Không tính được:", e)
code
Không tính được: Danh sách điểm rỗng

raise không có đối số trong khối except sẽ ném lại lỗi đang xử lý — hữu ích khi muốn ghi log rồi vẫn báo lỗi cho nơi gọi:

code
try:
    ...
except ValueError:
    print("Ghi log: dữ liệu lỗi")
    raise

Với ứng dụng lớn hơn, định nghĩa exception riêng để nơi gọi phân biệt được lỗi của mình với lỗi thư viện:

code
class DuLieuLoi(ValueError):
    pass

Kế thừa từ lớp phù hợp (ValueError ở đây) giúp code cũ vẫn bắt được.

EAFP và LBYL

Hai phong cách kiểm tra:

Phong cáchÝ nghĩaVí dụ
LBYLLook Before You Leap — kiểm tra trướcif "b" in tong:
EAFPEasier to Ask Forgiveness — thử rồi bắt lỗitry: tong["b"]
code
tong = {"a": 100}

if "b" in tong:          # LBYL
    print(tong["b"])
else:
    print("chưa có")

try:                     # EAFP
    print(tong["b"])
except KeyError:
    print("chưa có")
code
chưa có
chưa có

Python nghiêng về EAFP ở nơi có race condition hoặc kiểm tra tốn kém (file có thể bị xóa giữa lúc kiểm tra và lúc mở). Nhưng với lỗi dữ liệu hàng loạt trong CSV, LBYL kèm đếm lỗi thường dễ đọc hơn.

Với dict, cách gọn nhất là get — không cần cả hai khối trên:

code
print(tong.get("b", "chưa có"))

Gỡ lỗi nhanh

  • Đọc dòng cuối traceback trước, rồi tới dòng code được chỉ.
  • In biến ngay trước dòng lỗi: print(type(row), row).
  • repr() cho thấy khoảng trắng và ký tự ẩn: repr(row["so_luong"]) → ' 2' — một khoảng trắng thừa là đủ làm int() lỗi.
  • Kiểm tra len() trước khi lấy chỉ số.

Thực hành

  1. Cố ý gây ra cả sáu lỗi trong bảng và đọc traceback từng lần.
  2. Viết doc_so với đủ try/except/else/finally, in ra từng nhánh được chạy.
  3. Thử except (TypeError, ValueError) với None, "abc", "1,200".
  4. Đếm số dòng lỗi thay vì pass im lặng.
  5. Viết hàm ném ValueError khi danh sách rỗng.
  6. So sánh LBYL và EAFP trên cùng một dict.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Chương trình dừng, in tracebackException không được bắtĐọc dòng cuối, thêm try/except
except không chạyBắt sai loại lỗiIn type(e).__name__ để xem thật
Lỗi bị che mấtexcept Exception quá rộngBắt loại cụ thể
Dữ liệu sai biến mấtexcept: passGhi log/đếm lỗi
UnboundLocalError trong exceptDùng biến chưa gán khi try lỗi sớmKhởi tạo trước try
Mất traceback gốcraise ValueError(...) không kèm from eDùng raise ... from e
int() lỗi dù thấy sốKhoảng trắng ẩnstrip() trước, kiểm tra repr
finally chạy dù đã returnHành vi chuẩnDùng finally để dọn tài nguyên

Tóm tắt

  • Đọc traceback từ dòng cuối lên.
  • Sáu lỗi chính: KeyError, IndexError, ValueError, TypeError, AttributeError, FileNotFoundError.
  • try/except/else/finally: else cho luồng thành công, finally để dọn dẹp.
  • Bắt cụ thể, không nuốt lỗi, không bọc cả khối lớn.
  • raise để báo dữ liệu không hợp lệ; exception riêng cho ứng dụng lớn.
  • EAFP khi tình huống có thể đổi giữa lúc kiểm tra và lúc dùng.

Dùng ← → để chuyển bài