Decorator trong Python: cái @ thực ra làm gì?
Decorator thực chất là gì: cái @ đó là phép thuật gì
Bài 6 - series "Python cho dân backend".
Bây gặp @ khắp nơi: @app.get("/"), @lru_cache, @staticmethod, @pytest.fixture. Nhiều người dùng theo kiểu "thấy mẫu thì copy", coi nó là cú pháp thần bí. Đố: cái @ đó thực ra làm gì? Hiểu rồi thì bây tự viết được, và đọc framework hết thấy ma thuật.
Dùng AI coding agent? Đây là chỗ AI rất hay viết code "chạy được" mà làm framework/debug khổ. Nó có thể viết decorator không dùng
functools.wraps, làm mất tên hàm nên OpenAPI/log/test report hiện toànwrapper. Hoặc nó viết wrapper thiếu*args, **kwargs, chạy đúng với một hàm demo rồi bể khi gặp handler có tham số. Khi review code AI có@..., bây phải nhìn coi: decorator chạy lúc nào, nó bọc hàm ra sao, có giữ metadata và truyền đủ tham số hông?
Đa số nghĩ: decorator là cú pháp đặc biệt khó hiểu
Không hề. Sự thật gọn lỏn:
Decorator chỉ là một hàm nhận vào một hàm và trả về một hàm (thường là bản "bọc" thêm chức năng).
@decođặt trêndef fchỉ là đường tắt củaf = deco(f).
Hết. Không phép thuật gì.
Tự viết một cái cho thấy
Ví dụ decorator đo thời gian chạy:
import time
def do_thoi_gian(func):
def wrapper(*args, **kwargs):
t0 = time.time()
kq = func(*args, **kwargs) # gọi hàm gốc
print(f"{func.__name__} mất {time.time()-t0:.3f}s")
return kq
return wrapper # trả về bản bọc
@do_thoi_gian
def xu_ly_don_hang(n):
time.sleep(n)
return "xong"
# Dòng @ ở trên TƯƠNG ĐƯƠNG:
# xu_ly_don_hang = do_thoi_gian(xu_ly_don_hang)
Giờ gọi xu_ly_don_hang(1) thực ra là gọi wrapper, nó bấm giờ, gọi hàm gốc, in thời gian, trả kết quả. Cái *args, **kwargs cho wrapper nhận mọi tham số rồi chuyển thẳng cho hàm gốc - nhờ vậy decorator dùng được cho hàm bất kỳ.
Flow e2e: @app.get("/orders") chạy lúc nào?
Cái dễ lẫn nhứt: decorator chạy lúc Python import module / định nghĩa hàm, không phải đợi request tới mới chạy.
Ví dụ:
@app.get("/orders")
def list_orders():
return ["banh", "tra"]
Python hiểu gần như vầy:
decorator = app.get("/orders") # nhận path, trả về decorator
list_orders = decorator(list_orders) # đăng ký handler rồi trả hàm/wrapper
Flow thiệt trong backend:
- App start, Python import file route.
app.get("/orders")chạy, tạo decorator có nhớ path/orders.- Decorator nhận
list_orders, ghi nó vô bảng route của framework. - Tới lúc request
/orderstới, framework tra bảng route rồi mới gọi hàm đã đăng ký.
Vậy nên @app.get(...) không phải "hàm chạy nền". Nó là lúc framework đăng ký luật chơi trước, request tới sau mới chạy logic.
Bẫy: wrapper "nuốt" mất danh tính hàm gốc
Sau khi decorate, xu_ly_don_hang.__name__ ra "wrapper", docstring cũng mất → log/tài liệu/debug rối. Sửa bằng functools.wraps:
from functools import wraps
def do_thoi_gian(func):
@wraps(func) # giữ tên + docstring của func gốc
def wrapper(*args, **kwargs):
...
return wrapper
@wraps nên là thói quen mặc định mỗi khi viết decorator.
Ứng dụng backend (vì sao đáng học)
Decorator có mặt khắp backend Python vì nó tách "việc phụ" ra khỏi "logic chính" gọn gàng:
- Đo thời gian / logging (như trên).
- Cache:
@lru_cachenhớ kết quả theo tham số (nhớ caching ở System Design). - Auth / phân quyền:
@require_loginchặn trước khi vào handler. - Retry: bọc thêm thử lại khi lỗi (nhớ retry ở Networking).
- Đăng ký route:
@app.get("/")của FastAPI/Flask chính là decorator ghi hàm vào bảng route.
(Decorator có tham số - như @app.get("/x") - là một tầng nữa: một hàm nhận tham số, trả về decorator, decorator mới nhận hàm. Ba tầng. Hiểu hai tầng trên rồi thì tầng này chỉ là bọc thêm.)
Checklist: bây nắm chưa?
- Decorator thực chất là gì? (Hàm nhận một hàm, trả về một hàm.)
@decotrêndef ftương đương gì? (f = deco(f).)*args, **kwargstrong wrapper để làm gì? (Nhận mọi tham số rồi chuyển cho hàm gốc → dùng được cho hàm bất kỳ.)- Bẫy mất tên/docstring sửa bằng gì? (
functools.wraps.) - Kể vài ứng dụng backend? (Timing/log, cache
lru_cache, auth, retry, đăng ký route.) @app.get("/x")có gì khác decorator thường? (Là decorator CÓ tham số - thêm một tầng hàm.)
Bài tới: "Generator & lazy evaluation - xử file 10GB mà không nổ RAM."
Link bài gốc: https://quakebaynghe.substack.com/p/python-decorator-wrapper
All rights reserved