Lekcje modułu (4/4)
Dekoratory: funkcje modyfikujące funkcje
A dekorator (decorator) to funkcja, która przyjmuje inną funkcję i zwraca jej "udekorowaną" wersję. Jest to potężny sposób na dodawanie zachowań (logowanie, pomiar czasu, buforowanie/caching, ponowne próby, walidacja) bez modyfikowania wnętrza oryginalnej funkcji.
Funkcje jako wartości
W Pythonie funkcje są obiektami: możesz je przekazywać, zwracać oraz przypisywać.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Minimalny dekorator
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
# 5Składnia @decorator
Przypisanie somma = log(somma) można zapisać bardziej elegancko za pomocą @:
@log
def somma(a, b):
return a + bTo dokładnie to samo. Zapis @log nad definicją jest równoważny z: „jak tylko ta funkcja zostanie zdefiniowana, zastąp ją przez log(funkcja)”.
*args, **kwargs: akceptowanie dowolnej sygnatury
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args gromadzi argumenty pozycyjne, a **kwargs argumenty nazwane. Pozwalają one pisać wrappery działające z dowolną funkcją.
functools.wraps: zachowywanie metadanych
Bez użycia wraps udekorowana funkcja traci atrybuty __name__, __doc__ oraz podpowiedzi typów:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperPrzykład: @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()Dekoratory z biblioteki standardowej (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— zapamiętuje (cache-uje) wyniki czystej funkcji.@property— udostępnia metodę jako atrybut (widzieliśmy to w module OOP).@staticmethod,@classmethod— zmieniają sposób przekazywania parametruself.
Dekoratory z argumentami
Dekoratory mogą również przyjmować argumenty. Aby to zaimplementować, musisz zapisać trzy poziomy zagnieżdżonych funkcji: zewnętrzna funkcja przyjmuje argumenty dekoratora, środkowa przyjmuje dekorowaną funkcję, a wewnętrzna (wrapper) przyjmuje rzeczywiste parametry przekazywane do dekorowanej funkcji podczas wywołania.
Spróbuj sam
Napisz dekorator `double_result`, który wywołuje dekorowaną funkcję i zwraca jej wynik pomnożony przez 2. Zastosuj go do funkcji `f(x)` zwracającej x + 1. Oceń `f(10)`.
Pokaż wskazówkę
Wrapper wywołuje func i zwraca jej wynik pomnożony przez 2.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie powtórzeniowe
Użyj functools.lru_cache do przyspieszenia rekurencyjnej funkcji fib(n). Oblicz fib(30) i przypisz wynik do `r`. Oceń `r`.
Pokaż wskazówkę
@lru_cache(maxsize=None) nad instrukcją def.
Rozwiązanie dostępne po 3 próbach
Dodatkowe wyzwanie
Zdefiniuj dekorator `double_result` który podwaja (mnoży przez 2) wartość zwracaną przez dekorowaną funkcję. Zastosuj dekorator do funkcji `def sum_nums(a, b): return a + b`. Na koniec oceń `sum_nums(3, 4)`.
Pokaż wskazówkę
Wrapper wywołuje func(*args, **kwargs) i mnoży wynik przez 2.
Rozwiązanie dostępne po 3 próbach