Salt la conținutul principal
Modulul 9 · Lecția 4 din 436/36 în curs~14 min
Lecții din modul (4/4)

Decoratori: funcții care modifică funcții

Un decorator este o funcție care preia o altă funcție și returnează a versiunea „decorată” a acestuia. Este o modalitate puternică de a adăuga comportament (înregistrare, sincronizare, stocare în cache, reîncercare, validare) fără a atinge corpul funcția originală.

Funcționează ca valori

În Python, funcțiile sunt obiecte: le puteți transmite, le puteți returna, le puteți atribui.

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

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

Un decorator minimal

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

@decorator sintaxă

Misiunea somma = log(somma) este scrisă mai elegant cu @:

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

Este exact același lucru. @log de mai sus definiția este echivalentă cu: „de îndată ce această funcție este definită, înlocuiți-o cu log(function)”.

*args, **kwargs: acceptați orice semnătură

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args colectează argumente de poziție, **kwargs argumente de cuvinte cheie. Au lăsat scrii wrapper-uri care funcționează cu orice funcție.

functools.wraps: păstrează metadatele

Fără wraps, funcția decorată pierde __name__, __doc__, indicii:

Python
from functools import wraps

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

Exemplu: @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()

Decoratori de la 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 — memorează rezultatele unei funcții pure.
  • @property — expune o metodă ca atribut (văzut în modulul OOP).
  • @staticmethod, @classmethod — modificați modul în care self este transmis.

Decoratori cu argumente

Decoratorii pot accepta și argumente. Pentru a implementa acest lucru, trebuie să scrieți trei niveluri de funcții imbricate: funcția cea mai exterioară primește argumentele decoratorului, funcția din mijloc primește funcția decorată, iar funcția cea mai interioară (wrapper) primește argumentele reale transmise funcției decorate în timpul apelurilor.

Încercați-l singur

Exercițiu#python.m9.l4.e1
Încercări: 0Se încarcă…

Scrieți un decorator `double_result` care apelează funcția decorată și returnează rezultatul acesteia * 2. Aplicați-l unei funcții `f(x)` care returnează x + 1. Evaluați `f(10)`.

Se încarcă editorul...
Afișează indiciu

Wrapper-ul apelează func și returnează rezultatul înmulțit cu 2.

Soluție disponibilă după 3 încercări

Exercițiu de revizuire

Exercițiu#python.m9.l4.e2
Încercări: 0Se încarcă…

Utilizați functools.lru_cache pentru a accelera o funcție recursivă fib(n). Calculați fib(30) și atribuiți lui `r`. Evaluează `r`.

Se încarcă editorul...
Afișează indiciu

@lru_cache(maxsize=None) deasupra def.

Soluție disponibilă după 3 încercări

Provocare suplimentară

Exercițiu#python.m9.l4.e3
Încercări: 0Se încarcă…

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

Se încarcă editorul...
Afișează indiciu

Wrapper-ul apelează func(*args, **kwargs) și înmulțește rezultatul cu 2.

Soluție disponibilă după 3 încercări