Trong bài này chúng ta sẽ cùng tìm hiểu cách xử lý ngoại lệ bằng try except trong Python: ngoại lệ là gì, cách bắt lỗi bằng try / except, khối else, finally và cách tự tạo ngoại lệ bằng raise. Đây là kỹ năng bắt buộc khi viết tool đọc dữ liệu từ vi điều khiển, nơi mà dữ liệu nhận về không phải lúc nào cũng đúng định dạng.
Bài 13 trong Serie Lập trình Python từ A tới Z
Ngoại lệ trong Python là gì?
Ngoại lệ (exception) là lỗi xảy ra trong lúc chương trình đang chạy. Khác với lỗi cú pháp (SyntaxError) bị phát hiện ngay từ đầu, ngoại lệ chỉ xuất hiện khi gặp dữ liệu hoặc tình huống cụ thể.
Ví dụ int("abc") cú pháp hoàn toàn đúng, nhưng chạy tới đó thì Python báo ValueError vì “abc” không đổi được thành số. Nếu không xử lý, chương trình dừng ngay lập tức và in ra một đoạn thông báo lỗi màu đỏ.
Một số ngoại lệ hay gặp:
- ValueError: giá trị không hợp lệ, ví dụ
float("abc"). - TypeError: sai kiểu dữ liệu, ví dụ
1 + "2". - ZeroDivisionError: chia cho 0.
- KeyError: truy cập key không có trong Dictionary.
- IndexError: truy cập vượt quá số phần tử của List.
- FileNotFoundError: mở file không tồn tại.
Tại sao phải xử lý ngoại lệ?
Lấy ví dụ đời thường nhé. Bạn đi xe máy, đường có ổ gà. Không xử lý ngoại lệ giống như gặp ổ gà là ngã luôn, kết thúc chuyến đi. Có xử lý ngoại lệ thì gặp ổ gà sẽ lách qua, rồi đi tiếp.
Trong lập trình nhúng, chuyện này xảy ra liên tục. Tool Python đọc dữ liệu UART từ ESP32, chỉ cần nhiễu một byte hoặc rút cáp giữa chừng là nhận về một dòng rác. Không bắt lỗi thì tool chết, mất luôn cả buổi log dữ liệu.
Cú pháp try except trong Python
Đoạn code có thể gây lỗi đặt trong khối try. Nếu lỗi xảy ra, Python nhảy sang khối except thay vì dừng chương trình.
try:
# code có thể gây lỗi
except LoaiLoi as e:
# xử lý khi có lỗi, e chứa thông tin lỗi
else:
# chạy khi KHÔNG có lỗi
finally:
# luôn chạy, dù có lỗi hay không
Khối else và finally là tùy chọn, không bắt buộc phải có.
Ví dụ: tách dữ liệu từ vi điều khiển
Giả sử vi điều khiển gửi lên các dòng dạng TEMP:28.5. Trong số đó có vài dòng bị lỗi do nhiễu.
def parse_line(line):
"""Tách một dòng 'TEMP:28.5' gửi từ vi điều khiển thành số thực"""
key, value = line.split(":")
return float(value)
lines = ["TEMP:28.5", "TEMP:abc", "TEMP29.0", "TEMP:30.1"]
for line in lines:
try:
temp = parse_line(line)
except ValueError as e:
print(f"Bỏ qua dòng lỗi '{line}': {e}")
else:
print(f"Đọc được nhiệt độ {temp} độ C")
Dòng TEMP:abc lỗi ở float(), dòng TEMP29.0 thiếu dấu hai chấm nên lỗi ở bước tách chuỗi. Cả 2 lỗi đều là ValueError nên bị bắt chung một khối except. Chương trình không dừng mà tiếp tục xử lý dòng TEMP:30.1 phía sau.
Khối else chỉ chạy khi try không có lỗi. Đặt phần in kết quả vào else giúp khối try chỉ chứa đúng đoạn code có thể gây lỗi, đọc code dễ hiểu hơn.
Bắt nhiều loại ngoại lệ
Một đoạn code có thể gây nhiều loại lỗi khác nhau, mỗi loại cần xử lý riêng. Chúng ta viết nhiều khối except liên tiếp:
def average(values):
try:
return sum(values) / len(values)
except ZeroDivisionError:
print("Danh sách rỗng, không tính được trung bình")
return None
except TypeError:
print("Danh sách có phần tử không phải số")
return None
print("Trung bình:", average([1, 2, 3]))
print("Trung bình:", average([]))
print("Trung bình:", average([1, "2", 3]))
Python kiểm tra lần lượt từ trên xuống, gặp khối except nào khớp với loại lỗi thì chạy khối đó.
Lưu ý: Đừng viết except: trống không hoặc except Exception: cho mọi trường hợp. Cách này nuốt sạch mọi lỗi, kể cả lỗi gõ sai tên biến (NameError), khiến chương trình chạy sai mà không hề báo gì. Hãy bắt đúng loại lỗi mà bạn biết cách xử lý.
Khối finally
Khối finally luôn được chạy, dù trong try có lỗi hay không, thậm chí cả khi đã gặp return. Vì vậy finally thường dùng để dọn dẹp tài nguyên như đóng file, đóng cổng serial, ngắt kết nối mạng.
def read_config(path):
f = None
try:
f = open(path, encoding="utf-8")
return f.read()
except FileNotFoundError:
print(f"Không tìm thấy file {path}")
return ""
finally:
if f:
f.close()
print("Đã chạy khối finally")
read_config("khong_ton_tai.txt")
Dù file có tồn tại hay không, dòng “Đã chạy khối finally” vẫn được in ra. Ở bài Cách đọc và ghi file trong Python mình có giới thiệu câu lệnh with, thực chất with chính là cách viết gọn của try / finally cho việc đóng file đấy.
Lưu ý: Khi làm việc với cổng serial trong Jupyter Notebook hoặc trình thông dịch, nếu code lỗi giữa chừng mà không đóng cổng thì cổng vẫn bị giữ. Lần mở lại sẽ báo could not open port ... PermissionError trên Windows, phải khởi động lại kernel mới mở được. Luôn đặt ser.close() trong khối finally nhé.
Tự tạo ngoại lệ với raise
Ngoài bắt lỗi của Python, chúng ta có thể tự phát ra lỗi bằng từ khóa raise khi phát hiện tình huống bất thường. Còn muốn có loại lỗi riêng thì tạo một Class kế thừa từ Exception, như đã học ở bài Lập trình hướng đối tượng với Class.
class SensorError(Exception):
pass
def read_sensor(raw):
if raw is None:
raise SensorError("Cảm biến không phản hồi")
return raw * 0.0625
for raw in [456, None]:
try:
print("Nhiệt độ:", read_sensor(raw))
except SensorError as e:
print("Lỗi cảm biến:", e)
Hệ số 0.0625 chính là độ phân giải 12 bit của cảm biến DS18B20: giá trị thô 456 tương ứng 456 × 0.0625 = 28.5 độ C. Khi cảm biến không trả về gì, hàm read_sensor() chủ động báo SensorError để nơi gọi tự quyết định xử lý thế nào.
Kết quả
Code đầy đủ nằm trong file main.py trên GitHub. Chạy python main.py sẽ được:
Đọc được nhiệt độ 28.5 độ C Bỏ qua dòng lỗi 'TEMP:abc': could not convert string to float: 'abc' Bỏ qua dòng lỗi 'TEMP29.0': not enough values to unpack (expected 2, got 1) Đọc được nhiệt độ 30.1 độ C Trung bình: 2.0 Danh sách rỗng, không tính được trung bình Trung bình: None Danh sách có phần tử không phải số Trung bình: None Không tìm thấy file khong_ton_tai.txt Đã chạy khối finally Nhiệt độ: 28.5 Lỗi cảm biến: Cảm biến không phản hồi
Cả 4 tình huống lỗi đều được xử lý, chương trình chạy tới dòng cuối cùng mà không bị dừng giữa chừng.
Kết
Try, Except giúp chương trình Python “chịu đòn” tốt hơn, đặc biệt quan trọng với các tool làm việc với phần cứng, nơi dữ liệu và kết nối luôn có thể trục trặc. Nguyên tắc nhớ: bắt đúng loại lỗi, khối try càng ngắn càng tốt, dọn dẹp tài nguyên trong finally.
Tới đây các bạn đã xong Phần 1: Python cơ bản. Nếu thấy bài viết hay, hãy chia sẻ cho bạn bè và tham gia nhóm Nghiện lập trình để cùng trao đổi nhé!

