Chuyển đến nội dung chính
Mô-đun 9 · Bài học 4 trong tổng số 436/36 trong khóa học~14 min
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.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Một trang trí tối thiểu

Python
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
# 5

Cú pháp @decorator

Bài tập somma = log(somma) được viết trang nhã hơn với @:

Python
@log
def somma(a, b):
    return a + b

Nó 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ý

Python
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 ý:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Ví dụ: @timeit

Python
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

Python
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ách self đượ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ử

tập thể dục#python.m9.l4.e1
Nỗ lực: 0Đang tải…

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)`.

Đang tải trình chỉnh sửa…
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

tập thể dục#python.m9.l4.e2
Nỗ lực: 0Đang tải…

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`.

Đang tải trình chỉnh sửa…
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

tập thể dục#python.m9.l4.e3
Nỗ lực: 0Đang tải…

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)`.

Đang tải trình chỉnh sửa…
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ử