Câu 1Cái nào dưới đây có cùng nghĩa với việc đặt @logger phía trên def greet(): ...?
Decorator — Thêm hành vi cho hàm với @
Học decorator trong Python với cú pháp @ để thêm hành vi cho hàm mà không sửa code gốc, áp dụng được vào các framework thực tế.
Đến cuối bài trước về lambda, bạn đã thấy các cách chính để coi hàm như giá trị. Để khép lại, hãy chốt decorator — cú pháp chuyên dụng để xếp hành vi bổ sung lên trên một hàm.
Decorator là gì?
Decorator là cách thêm hành vi trước và sau một hàm mà không thay đổi chính hàm đó. Những thứ như "log lệnh gọi," "đo thời gian chạy," hay "cache kết quả" — hành vi chung mà bạn muốn xếp lên nhiều hàm — có thể sống ở một nơi.
Cú pháp chỉ là một dòng phía trên định nghĩa hàm: @tên_decorator. Python đọc đó là "giống func = tên_decorator(func)".
@logger phía trên def greet(): khiến Python chạy greet = logger(greet) bên trong, thay greet bằng một hàm mới được logger bao bọc.# Chính decorator (một hàm bậc cao nhận và trả về một hàm)
def logger(func):
def wrapper():
print("=== bắt đầu ===")
func() # gọi hàm gốc
print("=== kết thúc ===")
return wrapper
# Phía bên gọi: chỉ thêm @
@logger
def greet(): # → logger(greet)
print("Xin chào")
greet()
# === bắt đầu ===
# Xin chào
# === kết thúc ===
# Tương đương bên trong với:
# def greet():
# print("Xin chào")
# greet = logger(greet)
Decorator cơ bản — Bọc hàm bằng wrapper
Bộ khung của decorator là hình dạng 3 bước: hàm bên ngoài nhận func, hàm bên trong (theo quy ước là wrapper) gọi func(), và bạn return wrapper. wrapper giữ func khả dụng khi nó chạy chính xác là một closure.
Bất cứ thứ gì bạn viết trước và sau func() chạy mỗi lần hàm được trang trí được gọi.
def logger(func):
def wrapper(*args, **kwargs):
print(f"[LOG] đang chạy {func.__name__}")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} xong")
return result
return wrapper
@logger
def greet(name):
return f"Xin chào, {name}"
print(greet("Minh"))
# [LOG] đang chạy greet
# [LOG] greet xong
# Xin chào, Minh
- greet được thay bằng hàm wrapper
- Thân
greetgốc sống tiếp dưới dạngfuncbên trongwrapper
funcgiữgreetgốc- Xây
wrapperbên trong và trả về
- Chạy pre →
func()→ post theo thứ tự - Từ bên ngoài, đây là
greetmới
Truyền bất kỳ tham số nào qua với *args / **kwargs
Đến giờ, wrapper không nhận tham số. Khi bạn muốn trang trí các hàm có nhận tham số, dùng *args / **kwargs để nhận bất kỳ tham số nào nguyên dạng và chuyển tiếp thẳng tới `func`.
Điều đó biến decorator thành decorator chung hoạt động với mọi chữ ký hàm. Nó xử lý add(2, 3) (vị trí) và add(2, 3, name="ABC") (từ khóa) với cùng decorator.
def log_call(func):
def wrapper(*args, **kwargs):
print(f"call: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs) # unpack và chuyển tiếp ở đây cũng
print(f"result: {result}")
return result # đừng quên return nó
return wrapper
@log_call
def add(a, b):
return a + b
print(add(2, 3))
# call: args=(2, 3), kwargs={}
# result: 5
# 5
print(add(2, b=3))
# call: args=(2,), kwargs={'b': 3}
# result: 5
# 5
Đừng quên return Kết quả
Nếu wrapper chỉ viết result = func(...) và quên return, giá trị trả về của hàm được trang trí âm thầm biến thành None. Tai nạn kinh điển là phát hiện add(2, 3) lặng lẽ trả về None — khi bạn viết decorator, coi return result như phần của cùng phản xạ cơ bắp.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Code này in gì?def deco(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@deco
def plus(a, b):
return a + b
print(plus(3, 4))