Cơ bảnKiến thức cơ bản

Xử lý lỗi trong Python: try, except, finally

8 phút đọc0 lượt xem
#python#try except#exception handling#xử lý lỗi#python cơ bản#raise#custom exception#finally

Xử lý lỗi trong Python: try, except, finally từ A đến Z

Bạn đã từng viết một đoạn code Python chạy ngon lành, nhưng khi người dùng nhập sai dữ liệu — thay vì nhập số lại nhập chữ — thì chương trình đột ngột bị crash với một đống thông báo lỗi đỏ lòe? Đừng lo, đây là trải nghiệm của mọi lập trình viên Python khi mới bắt đầu.

Tin tốt là Python cung cấp một cơ chế rất mạnh để xử lý tình huống này: try/except/finally. Sau khi đọc bài viết này, bạn sẽ biết cách "bắt" lỗi trước khi nó làm sập chương trình, xử lý từng loại lỗi một cách thông minh, và tự tạo ra các loại lỗi riêng phù hợp với ứng dụng của mình.

Nếu bạn chưa quen với Python, hãy đọc bài Python là gì? trước để có nền tảng cơ bản.

1. Exception trong Python là gì?

Trước tiên, hãy phân biệt hai loại lỗi thường gặp khi lập trình:

Syntax Error — lỗi cú pháp

Đây là lỗi bạn viết code sai cú pháp, Python phát hiện được trước khi chạy:

# Lỗi cú pháp — thiếu dấu hai chấm
if x > 0
    print("Số dương")
# SyntaxError: expected ':'

Exception — lỗi trong lúc chạy (runtime)

Exception (ngoại lệ) là lỗi xảy ra trong quá trình chương trình đang chạy. Code của bạn viết đúng cú pháp, nhưng khi thực thi gặp tình huống không hợp lệ:

x = 10 / 0       # ZeroDivisionError: division by zero
y = int("hello") # ValueError: invalid literal for int()
z = [1,2,3][10]  # IndexError: list index out of range

Khi exception xảy ra, Python sẽ "ném" (raise) nó. Nếu không có gì "bắt" (catch) lại, chương trình sẽ dừng ngay lập tức và in ra thông báo lỗi:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
    x = int("hello")
ValueError: invalid literal for int() with base 10: 'hello'

Nhiệm vụ của chúng ta là bắt exception này và xử lý nó một cách khéo léo, thay vì để chương trình bị crash.

2. try/except — Cú pháp cơ bản

Đây là cú pháp cơ bản nhất để xử lý lỗi trong Python:

try:
    # Code có khả năng gây ra lỗi
    x = int("hello")
except ValueError:
    # Xử lý khi gặp ValueError
    print("Không thể chuyển đổi thành số!")

Cách hoạt động:

  1. Python chạy code trong khối try
  2. Nếu có exception xảy ra, Python ngay lập tức nhảy sang khối except
  3. Nếu không có exception, khối except bị bỏ qua hoàn toàn

Ví dụ thực tế: Nhận số từ người dùng

def nhap_so():
    try:
        so = int(input("Nhập một số nguyên: "))
        return so
    except ValueError:
        print("Lỗi! Bạn cần nhập một số nguyên hợp lệ.")
        return None

ket_qua = nhap_so()
if ket_qua is not None:
    print(f"Số bạn nhập: {ket_qua}")

Bây giờ dù người dùng nhập "abc" hay "một hai ba", chương trình vẫn không bị crash!

Bắt nhiều loại exception

def chia_so(a, b):
    try:
        ket_qua = a / b
        print(f"Kết quả: {ket_qua}")
    except ZeroDivisionError:
        print("Lỗi: Không thể chia cho 0!")
    except TypeError:
        print("Lỗi: Vui lòng nhập số!")

chia_so(10, 2)    # Kết quả: 5.0
chia_so(10, 0)    # Lỗi: Không thể chia cho 0!
chia_so(10, "a")  # Lỗi: Vui lòng nhập số!

Lấy thông tin chi tiết về lỗi

try:
    x = int("hello")
except ValueError as e:
    print(f"Có lỗi xảy ra: {e}")
    # In ra: Có lỗi xảy ra: invalid literal for int() with base 10: 'hello'

Dùng as e để lưu đối tượng exception vào biến e, từ đó có thể in ra thông báo lỗi chi tiết.

3. Các loại exception phổ biến trong Python

Dưới đây là những exception bạn sẽ gặp thường xuyên nhất:

Exception Nguyên nhân Ví dụ
ValueErrorGiá trị không hợp lệint("abc")
TypeErrorSai kiểu dữ liệu"5" + 5
ZeroDivisionErrorChia cho số 010 / 0
IndexErrorIndex ngoài phạm vi[1,2,3][10]
KeyErrorKey không tồn tại trong dict{"a":1}["b"]
FileNotFoundErrorFile không tồn tạiopen("khong_ton_tai.txt")
AttributeErrorThuộc tính không tồn tại"hello".unknown()
NameErrorBiến chưa được khai báoprint(bien_chua_co)
ImportErrorImport module thất bạiimport module_ao

Code minh họa từng loại

# KeyError
try:
    thong_tin = {"ten": "An", "tuoi": 20}
    print(thong_tin["email"])
except KeyError as e:
    print(f"Không tìm thấy key: {e}")

# IndexError
try:
    danh_sach = [1, 2, 3]
    print(danh_sach[10])
except IndexError as e:
    print(f"Vị trí không hợp lệ: {e}")

# AttributeError
try:
    so = 42
    so.upper()
except AttributeError as e:
    print(f"Thuộc tính không tồn tại: {e}")

4. try/except/else/finally — Cú pháp đầy đủ

Ngoài tryexcept, Python còn cho phép thêm hai khối nữa: elsefinally.

try:
    # Code có thể gây lỗi
    pass
except SomeException:
    # Chạy khi có exception
    pass
else:
    # Chạy khi try KHÔNG có exception
    pass
finally:
    # LUÔN LUÔN chạy, dù có lỗi hay không
    pass

Khối else — chạy khi không có lỗi

Bạn có thể thắc mắc: "Tại sao cần else? Không phải đặt code sau try/except cũng được sao?" Câu trả lời là: code trong else chỉ chạy khi try thành công — nó giúp bạn phân biệt rõ ràng code "xử lý thành công" và code "có thể gây lỗi".

def doc_file_cau_hinh(ten_file):
    try:
        f = open(ten_file, "r")
        noi_dung = f.read()
        f.close()
    except FileNotFoundError:
        print(f"Không tìm thấy file: {ten_file}")
        return None
    else:
        print("Đọc file thành công!")
        return noi_dung

Khối finally — luôn luôn chạy

Đây là điểm mà nhiều người mới học hay nhầm: finally chạy dù có lỗi hay không, dù hàm có return hay không. Nó thường dùng để "dọn dẹp" — đóng file, ngắt kết nối database, giải phóng tài nguyên.

def tinh_chia(a, b):
    ket_qua = None
    try:
        ket_qua = a / b
        print(f"Kết quả: {ket_qua}")
    except ZeroDivisionError:
        print("Không thể chia cho 0!")
    finally:
        print("Đã hoàn thành phép tính.")
    return ket_qua

tinh_chia(10, 2)
# Kết quả: 5.0
# Đã hoàn thành phép tính.

tinh_chia(10, 0)
# Không thể chia cho 0!
# Đã hoàn thành phép tính.

5. Lệnh raise — Tự tạo lỗi

Đôi khi bạn muốn chủ động tạo ra một exception khi gặp dữ liệu không hợp lệ, thay vì để lỗi xảy ra sau ở chỗ khác (rất khó debug). Đó là lúc dùng lệnh raise.

def thiet_lap_tuoi(tuoi):
    if not isinstance(tuoi, int):
        raise TypeError("Tuổi phải là số nguyên")
    if tuoi < 0 or tuoi > 150:
        raise ValueError(f"Tuổi {tuoi} không hợp lệ (phải từ 0 đến 150)")
    return tuoi

try:
    thiet_lap_tuoi(-5)
except ValueError as e:
    print(f"Lỗi nhập liệu: {e}")

try:
    thiet_lap_tuoi("hai mươi")
except TypeError as e:
    print(f"Lỗi kiểu dữ liệu: {e}")

Hàm trong Python cũng hay dùng raise để kiểm tra đầu vào. Bạn có thể xem thêm ở bài Hàm trong Python.

Re-raise — ném lại exception

import logging

def xu_ly_du_lieu(du_lieu):
    try:
        ket_qua = thao_tac_rui_ro(du_lieu)
        return ket_qua
    except Exception as e:
        logging.error(f"Lỗi khi xử lý dữ liệu: {e}")
        raise  # Ném lại đúng exception ban đầu

6. Tạo Custom Exception

Khi xây dựng ứng dụng thực tế, các exception có sẵn của Python đôi khi quá chung chung. Bạn có thể tạo exception riêng bằng cách kế thừa từ lớp Exception.

class TuoiKhongHopLeError(Exception):
    """Exception khi tuổi nhập vào ngoài phạm vi hợp lệ"""
    pass

class EmailKhongHopLeError(Exception):
    """Exception khi email không đúng định dạng"""
    pass


def kiem_tra_tuoi(tuoi):
    if tuoi < 18:
        raise TuoiKhongHopLeError(f"Tuổi {tuoi}: Phải từ 18 tuổi trở lên")

def kiem_tra_email(email):
    if "@" not in email:
        raise EmailKhongHopLeError(f"Email '{email}' không hợp lệ")


try:
    kiem_tra_tuoi(15)
except TuoiKhongHopLeError as e:
    print(f"Không đủ điều kiện: {e}")

try:
    kiem_tra_email("khong-co-at-sign.com")
except EmailKhongHopLeError as e:
    print(f"Email lỗi: {e}")

Custom exception nâng cao — thêm thuộc tính

class DangKyLoi(Exception):
    """Lớp cơ sở cho các lỗi đăng ký"""
    pass

class TuoiKhongDuError(DangKyLoi):
    def __init__(self, tuoi, tuoi_toi_thieu=18):
        self.tuoi = tuoi
        self.tuoi_toi_thieu = tuoi_toi_thieu
        super().__init__(
            f"Tuổi {tuoi} chưa đủ điều kiện. Cần ít nhất {tuoi_toi_thieu} tuổi."
        )

class MatKhauYeuError(DangKyLoi):
    def __init__(self, do_dai, do_dai_toi_thieu=8):
        self.do_dai = do_dai
        self.do_dai_toi_thieu = do_dai_toi_thieu
        super().__init__(
            f"Mật khẩu chỉ có {do_dai} ký tự. Cần ít nhất {do_dai_toi_thieu} ký tự."
        )

7. Kết hợp with và try/except

Khi làm việc với file, có một vấn đề thường gặp: nếu lỗi xảy ra trong lúc đọc/ghi file, file sẽ không được đóng. Kết hợp withtry/except giải quyết triệt để vấn đề này.

# Cách AN TOÀN — with tự động đóng file dù có lỗi hay không
try:
    with open("data.txt", "r", encoding="utf-8") as f:
        noi_dung = f.read()
        print(noi_dung)
except FileNotFoundError:
    print("Không tìm thấy file data.txt")
except PermissionError:
    print("Không có quyền đọc file này")
except UnicodeDecodeError:
    print("Lỗi encoding — thử encoding khác")

Để hiểu sâu hơn về cách đọc và ghi file trong Python, xem bài Đọc và ghi file trong Python.

Sao chép file an toàn

def sao_chep_file(file_nguon, file_dich):
    try:
        with open(file_nguon, "r", encoding="utf-8") as f_doc:
            with open(file_dich, "w", encoding="utf-8") as f_ghi:
                f_ghi.write(f_doc.read())
        print(f"Đã sao chép '{file_nguon}' → '{file_dich}'")
    except FileNotFoundError:
        print(f"Không tìm thấy file nguồn: {file_nguon}")
    except PermissionError:
        print("Không có quyền truy cập file!")
    except Exception as e:
        print(f"Lỗi không xác định: {e}")

8. Ví dụ tổng hợp: Form đăng ký người dùng

Hãy kết hợp tất cả những gì đã học để xây dựng một module xử lý đăng ký người dùng hoàn chỉnh:

class LoDangKy(Exception):
    pass

class EmailSaiDinhDangError(LoDangKy):
    pass

class MatKhauKhongDuManh(LoDangKy):
    pass

class TenDangNhapDaTonTai(LoDangKy):
    pass


def kiem_tra_email(email: str) -> None:
    if not email or "@" not in email or "." not in email.split("@")[-1]:
        raise EmailSaiDinhDangError(f"Email '{email}' không đúng định dạng")

def kiem_tra_mat_khau(mat_khau: str) -> None:
    if len(mat_khau) < 8:
        raise MatKhauKhongDuManh("Mật khẩu cần ít nhất 8 ký tự")
    if mat_khau.isdigit():
        raise MatKhauKhongDuManh("Mật khẩu không được chỉ gồm chữ số")

USERS_DA_DANG_KY = {"admin@example.com", "user@vietcode.dev"}

def dang_ky(email: str, mat_khau: str) -> dict:
    try:
        kiem_tra_email(email)
        kiem_tra_mat_khau(mat_khau)
        if email in USERS_DA_DANG_KY:
            raise TenDangNhapDaTonTai(f"Email '{email}' đã được sử dụng")
        tai_khoan = {"email": email, "active": True}
        print(f"Đăng ký thành công: {email}")
        return tai_khoan
    except LoDangKy as e:
        print(f"Không thể đăng ký: {e}")
        return None
    finally:
        print(f"[LOG] Yêu cầu đăng ký cho email: {email}")


dang_ky("sai-email", "password123")
dang_ky("user@test.com", "123")
dang_ky("admin@example.com", "password123")
dang_ky("moi@test.com", "password123")

9. Những lưu ý quan trọng

Bắt exception cụ thể, không bắt tất cả

# Không nên — che giấu tất cả lỗi, rất khó debug
try:
    xu_ly_du_lieu()
except:
    pass

# Nên làm — chỉ bắt lỗi bạn biết cách xử lý
try:
    xu_ly_du_lieu()
except ValueError:
    print("Dữ liệu không hợp lệ")
except FileNotFoundError:
    print("File không tồn tại")

Dùng get() thay vì try/except cho dict

du_lieu = {"ten": "An", "tuoi": 20}
email = du_lieu.get("email", "chua_co@email.com")

Tổng kết

Xử lý lỗi là kỹ năng quan trọng giúp chương trình Python của bạn bền vững và thân thiện với người dùng. Cùng ôn lại những điểm chính:

  • Exception là lỗi xảy ra trong lúc chạy chương trình. Nếu không xử lý, chương trình sẽ crash.
  • try/except cho phép bắt exception và xử lý thay vì để chương trình dừng.
  • else chạy khi không có exception xảy ra trong khối try.
  • finally luôn luôn chạy — dùng để đóng file, kết nối, giải phóng tài nguyên.
  • raise cho phép tự tạo exception — hữu ích để validate dữ liệu đầu vào.
  • Custom exception giúp lỗi có ý nghĩa rõ ràng hơn trong ứng dụng của bạn.
  • with + try/except là cách tốt nhất để xử lý file an toàn.

Bước tiếp theo, hãy thực hành bằng cách thêm xử lý lỗi vào các chương trình bạn đã viết trước đây. Xem lại bài Hàm trong Python để kết hợp exception handling vào các hàm của bạn, hoặc thử với Đọc và ghi file trong Python — nơi exception handling là không thể thiếu.

Về tác giả

Ảnh đại diện tác giả Kenji — họa tiết hình học

Kenji

Kỹ sư phần mềm full-stack (Web), hơn 5 năm kinh nghiệm thực tế

  • Python
  • DB
  • Hạ tầng
  • Đào tạo & cố vấn
  • AI

Làm việc cùng đồng nghiệp người Việt, tôi thấy thiếu tài liệu kỹ thuật rõ ràng bằng tiếng Việt. codeahoc là nơi tôi chia sẻ theo hướng thực tế, dễ áp dụng.

Nguyên tắc nội dung

  • Ưu tiên nguồn gốc và góc nhìn từ thực tế triển khai.
  • Nếu có sai sót, nội dung sẽ được cập nhật và sửa kịp thời.

Khóa học liên quan

100 Days of Code: The Complete Python Pro Bootcamp

Học Python qua 100 dự án thực tế. Phù hợp cho người mới bắt đầu.

4.7499.000 ₫
Xem khóa học →

Python for Data Science and Machine Learning Bootcamp

Học Data Science với Python: pandas, matplotlib, scikit-learn.

4.6499.000 ₫
Xem khóa học →

Automate the Boring Stuff with Python

Tự động hóa công việc lặp đi lặp lại bằng Python.

4.6399.000 ₫
Xem khóa học →
Quảng cáo