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:
- Python chạy code trong khối
try - Nếu có exception xảy ra, Python ngay lập tức nhảy sang khối
except - Nếu không có exception, khối
exceptbị 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ụ |
|---|---|---|
ValueError | Giá trị không hợp lệ | int("abc") |
TypeError | Sai kiểu dữ liệu | "5" + 5 |
ZeroDivisionError | Chia cho số 0 | 10 / 0 |
IndexError | Index ngoài phạm vi | [1,2,3][10] |
KeyError | Key không tồn tại trong dict | {"a":1}["b"] |
FileNotFoundError | File không tồn tại | open("khong_ton_tai.txt") |
AttributeError | Thuộc tính không tồn tại | "hello".unknown() |
NameError | Biến chưa được khai báo | print(bien_chua_co) |
ImportError | Import module thất bại | import 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 try và except, Python còn cho phép thêm hai khối nữa: else và finally.
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 with và try/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.