
Các decorator phục vụ đo thời gian thực thi, thử lại, lưu bộ nhớ đệm và xác thực trong Python
Bài viết trình bày cơ chế vận hành chuyên sâu của các Python decorator, đồng thời cung cấp phương pháp xây dựng các decorator hoạt động tin cậy phục vụ đo thời gian thực thi (timing), thử lại (retries), lưu bộ nhớ đệm (caching) và xử lý dữ liệu đầu vào (input)...
Decorator phục vụ đo thời gian, thử lại, lưu bộ nhớ đệm và xác thực trong Python
Tác giả: Nahla Davies, ngày 6/11/2026, chuyên mục Practical Python 0
Chia sẻ
Đăng bài
Chia sẻ
Bài viết phân tích cơ chế hoạt động chuyên sâu của decorator trong Python và phương pháp xây dựng các decorator đáng tin cậy nhằm đo thời gian thực thi, tự động thử lại, lưu bộ nhớ đệm và xác thực dữ liệu đầu vào.
Các nội dung trọng tâm bao gồm:
Cách cú pháp @, closure và functools.wraps kết hợp để tạo ra một decorator bảo toàn hành vi và siêu dữ liệu (metadata) của hàm gốc.
Cách xây dựng decorator đo thời gian và decorator factory có khả năng tùy biến cấu hình để thử lại mà không bỏ sót ngoại lệ.
Thời điểm sử dụng functools.lru_cache và decorator xác thực dựa trên chữ ký hàm (signature), cũng như các trường hợp không nên áp dụng decorator.
Module đường ống xử lý đặc trưng ban đầu gồm ba hàm tinh gọn: một hàm tải các embedding từ kho lưu trữ cục bộ, một hàm truy xuất tập từ vựng từ dịch vụ nội bộ, và một hàm chuyển đổi dữ liệu theo lô (batch). Sau sáu tuần, một phần ba mã nguồn của mỗi hàm đã trở thành mã xử lý biên (boundary code). Hàm load_embeddings phải kiểm tra đường dẫn tồn tại và kích thước lô mang giá trị dương trước khi thực thi. Hàm fetch_vocab bao bọc yêu cầu trong vòng lặp for attempt in range(3) kèm lệnh time.sleep(). Hàm transform_batch ghi nhận time.perf_counter() hai lần để in khoảng thời gian chênh lệch, đồng thời sử dụng một từ điển (dictionary) ở cấp module để lưu kết quả theo khóa, tránh chuyển đổi trùng lặp một lô dữ liệu. Không phần mã xử lý biên nào thuộc nhiệm vụ cốt lõi của hàm. Toàn bộ phần mã này được sao chép thủ công, dẫn đến việc ba hàm thực hiện thao tác thử lại, đo thời gian và kiểm soát dữ liệu đầu vào theo ba phương thức khác nhau. Hàm tiếp theo được bổ sung sẽ có nguy cơ phát sinh phương thức thứ tư.
def fetch_vocab(name, attempts=3):
if not name:
raise ValueError("name must not be empty")
start = time.perf_counter()
for attempt in range(attempts):
try:
vocab = _request_vocab(name) # tác vụ thực tế
break
except TimeoutError:
if attempt == attempts - 1:
raise
time.sleep(0.5)
print(f"fetch_vocab took {time.perf_counter() - start:.3f}s")
return vocab
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fetch_vocab(name, attempts=3):
if not name:
raise ValueError("name must not be empty")
start = time.perf_counter()
for attempt in range(attempts):
try:
vocab = _request_vocab(name) # tác vụ thực tế
break
except TimeoutError:
if attempt == attempts - 1:
raise
time.sleep(0.5)
print(f"fetch_vocab took {time.perf_counter() - start:.3f}s")
return vocab
Các thao tác đo thời gian, thử lại, lưu bộ nhớ đệm và kiểm tra dữ liệu đầu vào đều diễn ra tại cùng một vị trí: thời điểm lệnh gọi bắt đầu đi vào hàm và thời điểm kết thúc lệnh gọi. Python cung cấp cấu trúc chuyên biệt cho vị trí này. Decorator tiếp nhận một hàm và trả về một hàm bao bọc (wrapper), cho phép thực thi mã lệnh trước và sau hàm gốc. Logic biên lặp lại khi đó chỉ cần khai báo một lần duy nhất trong hàm bao bọc và được áp dụng thông qua một dòng mã đặt phía trên mỗi từ khóa def. Khi cùng một logic phải thực thi bao quanh mọi lệnh gọi, việc liên tục viết lại bên trong từng hàm là không cần thiết. Phần lớn người học tiếp cận decorator dưới dạng công cụ sẵn có, như @lru_cache hoặc @app.route trong các framework web, và hiếm khi tự xây dựng. Khoảng trống này là nguyên nhân khiến decorator thử lại tự viết thường bỏ qua các ngoại lệ cần báo cáo. Bài viết tiếp cận theo hướng ngược lại: phân tích cú pháp, cơ chế closure bên dưới, phương pháp chuẩn hóa metadata cho hàm bao bọc, sau đó lần lượt xem xét bốn hành vi xử lý biên, và kết thúc bằng nguyên tắc xác định trường hợp không nên sử dụng decorator.
Decorator là cơ chế gán lại hàm với cú pháp tối ưu hơn
Dòng lệnh loại bỏ tính chất trừu tượng này thực chất là một phép gán. Giả sử có hàm announce tiếp nhận một hàm và trả về một hàm mới, hai cách định nghĩa hàm greet dưới đây đều liên kết cùng một loại đối tượng với cùng một tên gọi.
def announce(func):
def wrapper():
print("calling", func.__name__)
return func()
return wrapper
def greet():
return "hello"
greet = announce(greet) # greet is now the wrapper
@announce # identical outcome
def greet():
return "hello"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def announce(func):
def wrapper():
print("calling", func.__name__)
return func()
return wrapper
def greet():
return "hello"
greet = announce(greet) # greet is now the wrapper
@announce # identical outcome
def greet():
return "hello"
Bảng thuật ngữ của Python gọi cú pháp ký hiệu @ là cú pháp rút gọn (syntactic sugar) cho phép gán lại nói trên. Tài liệu tham khảo ngôn ngữ nêu rõ thời điểm diễn ra quá trình này: các biểu thức decorator được đánh giá khi hàm được định nghĩa, trong phạm vi chứa định nghĩa đó, và giá trị trả về sẽ được liên kết với tên hàm thay cho đối tượng hàm ban đầu. Yếu tố “khi hàm được định nghĩa” dẫn đến hai hệ quả. Bản thân decorator chỉ chạy một lần duy nhất tại thời điểm nạp mô-đun (import time), do đó bất kỳ thao tác nào được thực hiện bên ngoài wrapper đều diễn ra trước mọi lệnh gọi hàm, cho dù đó là việc đăng ký hàm vào bảng hay in một dòng thông báo. Đồng thời, giá trị do decorator trả về sẽ đại diện cho tên hàm greet kể từ thời điểm đó. Nếu một decorator không trả về giá trị, greet sẽ nhận giá trị None và lệnh gọi đầu tiên sẽ dẫn đến lỗi TypeError.
Các decorator xếp chồng hoạt động theo cơ chế lồng nhau. Cú pháp @f1 đặt trên @f2 và đặt trên def func tương đương với biểu thức func = f1(f2(func)); theo đó, decorator ở dưới cùng sẽ bao bọc hàm trước và decorator ở trên cùng sẽ bao bọc kết quả thu được. Thứ tự này giữ vai trò quyết định, quy định decorator nào tiếp nhận kết quả lưu trong bộ nhớ đệm (cache) và decorator nào tiếp nhận lỗi kiểm thực (validation error), nội dung này sẽ được phân tích lại ở phần cuối.
Cơ chế bao đóng (closure) giúp wrapper ghi nhớ hàm gốc
Hàm announce nêu trên chỉ hoạt động đối với các hàm không tiếp nhận đối số, do đó thiếu tính ứng dụng thực tiễn. Cấu trúc tổng quát bao gồm ba phần, và mọi decorator trong bài viết này đều là biến thể từ cấu trúc đó.
def announce(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
result = func(*args, **kwargs)
print("returned", result)
return result
return wrapper
1
2
3
4
5
6
7
def announce(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
result = func(*args, **kwargs)
print("returned", result)
return result
return wrapper
Hàm bên ngoài tiếp nhận đối số func, hàm wrapper bên trong thực hiện xử lý tại thời điểm gọi hàm, và hàm bên ngoài




Nguồn tin: Machine Learning Mastery — Tác giả: Nahla Davies. Bản dịch tiếng Việt do AI thực hiện, có thể có sai sót.