Bài học theo mô-đun (4/4)
Trình trang trí: các hàm sửa đổi hàm
trang trí là một hàm nhận một hàm khác và trả về một phiên bản "trang trí" của nó. Đó là một cách mạnh mẽ để thêm hành vi (ghi nhật ký, tính thời gian, lưu vào bộ nhớ đệm, thử lại, xác thực) mà không cần chạm vào phần thân của chức năng ban đầu.
Chức năng như giá trị
Trong Python, hàm là đối tượng: bạn có thể truyền, trả về, gán chúng.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Một trang trí tối thiểu
def log(func):
def wrapper(*args, **kwargs):
print(f"chiamo {func.__name__}")
risultato = func(*args, **kwargs)
print(f"ho finito {func.__name__}")
return risultato
return wrapper
def somma(a, b):
return a + b
somma = log(somma) # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5Cú pháp @decorator
Bài tập somma = log(somma) được viết trang nhã hơn với @:
@log
def somma(a, b):
return a + bNó hoàn toàn giống nhau. @log ở trên định nghĩa tương đương với:
"ngay sau khi hàm này được xác định, hãy thay thế nó bằng log(function)".
*args, **kwargs: chấp nhận mọi chữ ký
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args thu thập các đối số vị trí, **kwargs đối số từ khóa. Họ để
bạn viết các hàm bao hoạt động với bất kỳ chức năng nào.
functools.wraps: bảo toàn siêu dữ liệu
Không có wraps, hàm trang trí sẽ mất __name__, __doc__, gợi ý:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperVí dụ: @timeit
from functools import wraps
import time
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
risultato = func(*args, **kwargs)
dt = time.perf_counter() - t0
print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
return risultato
return wrapper
@timeit
def somma_grande():
return sum(range(10**6))
somma_grande()Trang trí từ stdlib
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n - 1) + fib(n - 2)
fib(100) # istantaneo grazie alla cache@lru_cache— ghi nhớ kết quả của một hàm thuần túy.@property— hiển thị một phương thức dưới dạng thuộc tính (được thấy trong mô-đun OOP).@staticmethod,@classmethod— thay đổi cáchselfđược thông qua.
Trang trí với các đối số
Người trang trí cũng có thể chấp nhận tranh luận. Để thực hiện điều này, bạn phải viết ba cấp độ của các hàm lồng nhau: hàm ngoài cùng nhận các đối số của trình trang trí, hàm ở giữa nhận hàm được trang trí và hàm trong cùng (wrapper) nhận các đối số thực tế được truyền cho hàm trang trí trong khi gọi.
Hãy tự mình thử
Viết một trình trang trí `double_result` gọi hàm được trang trí và trả về kết quả của nó * 2. Áp dụng nó cho hàm `f(x)` trả về x + 1. Đánh giá `f(10)`.
Hiển thị gợi ý
Trình bao bọc gọi func và trả về kết quả nhân với 2.
Giải pháp khả dụng sau 3 lần thử
Xem lại bài tập
Sử dụng functools.lru_cache để tăng tốc hàm fib(n) đệ quy. Tính fib(30) và gán cho `r`. Đánh giá `r`.
Hiển thị gợi ý
@lru_cache(maxsize=None) trên mức def.
Giải pháp khả dụng sau 3 lần thử
Thử thách bổ sung
Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.
Hiển thị gợi ý
Trình bao bọc gọi func(*args, **kwargs) và nhân kết quả với 2.
Giải pháp khả dụng sau 3 lần thử