Hướng Dẫn Viết Decorator Từ Đầu Trong Python: Từ Cấu Trúc Cơ Bản Đến Nâng Cao

Trong Python, Decorator là một trong những tính năng cực kỳ mạnh mẽ giúp bạn "bọc" và mở rộng hành vi của các hàm hoặc phương thức có sẵn mà không cần sửa đổi trực tiếp mã nguồn bên trong. Tuy nhiên, việc tự tay viết một Decorator hoàn chỉnh và chuẩn chỉnh cho môi trường Production lại khiến không ít lập trình viên bối rối.
Bài viết này sẽ dẫn dắt bạn đi qua 3 phần cốt lõi: Cấu trúc đúng chuẩn, kỹ thuật tối ưu Production và cách viết Decorator nhận tham số nâng cao.
🟩 PHẦN 1: CẤU TRÚC ĐÚNG (SYNTAX & STRUCTURE)
Một Decorator cơ bản trong Python thực chất là một hàm bậc cao (Higher-Order Function) tuân thủ cấu trúc lồng nhau nghiêm ngặt. Để viết đúng từ đầu, bạn cần nắm vững 6 nguyên tắc sau:
- Outer Function: Hàm cha nhận vào đúng 1 tham số đại diện cho hàm gốc (func hoặc original_function).
- Inner Function: Định nghĩa một hàm nội bộ bên trong (thường đặt tên chuẩn là wrapper).
- Flexible Arguments: Hàm wrapper phải linh hoạt nhận đầy đủ *args và **kwargs.
- Forwarding Arguments: Gọi hàm gốc bên trong wrapper và truyền chính xác tham số: original_function(*args, **kwargs).
- Capture Return Value: Lưu kết quả của hàm gốc vào một biến (result) rồi trả về ở cuối wrapper để tránh làm mất dữ liệu đầu ra.
- Return Wrapper: Hàm cha kết thúc bằng lệnh return wrapper (Tuyệt đối không viết nhầm thành return wrapper()).
def my_decorator(original_function):
# Tầng hàm nội bộ (wrapper) nhận linh hoạt mọi tham số
def wrapper(*args, **kwargs):
print("-> Trước khi hàm gốc chạy")
# Lưu kết quả và chuyển tiếp tham số cho hàm gốc
result = original_function(*args, **kwargs)
print("-> Sau khi hàm gốc chạy")
return result # Trả về kết quả nguyên bản của hàm gốc
return wrapper # Trả về chính hàm wrapper (không có dấu ngoặc tròn)
🟨 PHẦN 2: CHUẨN HOÁ PRODUCTION (BEST PRACTICES)
Khi đưa code lên môi trường thực tế (Production), nếu sử dụng Decorator thông thường, hàm gốc của bạn sẽ bị "mất tích" tên và tài liệu (__name__, __doc__) vì bị thay thế bởi hàm wrapper. Để khắc phục triệt để, bạn cần áp dụng chuẩn hóa:
1. Sử dụng @wraps từ thư viện functools
Luôn nhớ from functools import wraps và đặt @wraps(original_function) ngay phía trên hàm wrapper để bảo toàn danh tính nguyên thủy của hàm gốc.
2. Tách bạch tác vụ (Side Effects Separation)
Phân chia rõ ràng phần code chạy trước (before) và phần code chạy sau (after) lệnh gọi hàm gốc để cấu trúc decorator mạch lạc, dễ bảo trì.
from functools import wraps
import time
def measure_time(original_function):
@wraps(original_function) # Bảo toàn tên và docstring của hàm gốc
def wrapper(*args, **kwargs):
start_time = time.time()
# Thực thi hàm gốc
result = original_function(*args, **kwargs)
end_time = time.time()
print(f"Hàm [{original_function.__name__}] chạy mất: {end_time - start_time:.4f} giây")
return result
return wrapper
@measure_time
def heavy_calculation(limit: int) -> int:
"""Hàm tính toán nặng."""
return sum(i for i in range(limit))
# Sử dụng:
heavy_calculation(1000000)
🟪 PHẦN 3: NÂng CAO (DECORATOR CÓ THAM SỐ)
Nếu bạn muốn viết một Decorator có khả năng nhận riêng tham số cấu hình của nó (Ví dụ: @repeat(num_times=3)), cấu trúc 2 tầng hàm thông thường sẽ không đủ. Lúc này, bạn bắt buộc phải xây dựng cấu trúc 3 tầng hàm lồng nhau (Triple-Nested Structure):
- Tầng 1 (Outer nhất): Nhận tham số cấu hình của riêng Decorator (num_times).
- Tầng 2 (Middle): Nhận hàm gốc (original_function).
- Tầng 3 (Inner nhất): Hàm wrapper xử lý logic thực thi.
from functools import wraps
def repeat(num_times: int):
# Tầng 1: Nhận tham số cấu hình của Decorator
print(f"-> Đang cấu hình Decorator lặp lại: {num_times} lần")
def decorator_repeat(original_function):
# Tầng 2: Nhận hàm gốc
@wraps(original_function)
def wrapper(*args, **kwargs):
# Tầng 3: Thực thi logic lặp lại
result = None
for _ in range(num_times):
result = original_function(*args, **kwargs)
return result # Trả về kết quả của lần chạy cuối cùng
return wrapper
return decorator_repeat
# Sử dụng Decorator có tham số:
@repeat(num_times=3)
def say_hello(name: str):
print(f"Xin chào, {name}!")
say_hello("Python Dev")
📌 Lời Kết
Viết Decorator từ đầu đòi hỏi bạn phải nắm vững cách hoạt động của Closure và First-Class Functions trong Python. Bằng việc tuân thủ cấu trúc chuẩn, luôn dùng @wraps và thành thạo mô hình 3 tầng hàm, bạn hoàn toàn có thể tự thiết kế các công cụ bổ trợ cực kỳ chuyên nghiệp và an toàn cho mọi dự án Python lớn!
Từ khóa tìm hiểu thêm: Python Decorators from scratch, functools wraps Python, Nested Decorators Python, Advanced Python OOP.
Bình luận