ProgrammazioneSviluppatore Backend Python

Che cos'è un decoratore per le funzioni in Python, qual è la sua storia e a cosa serve nella programmazione moderna?

Supera i colloqui con l'assistente IA Hintsage

Risposta

Storia

I decoratori sono apparsi in Python come zucchero sintattico dalla versione 2.4, per semplificare il lavoro con le funzioni di ordine superiore — quelle che accettano o restituiscono altre funzioni. L'evoluzione degli approcci per estendere la funzionalità delle funzioni ha portato a un formato conciso ed espressivo — annotazioni tramite @decorator.

Problema

Nei grandi progetti è spesso necessario modificare o incapsulare funzioni con una logica aggiuntiva: registrazione, controllo accessi, caching, misurazione del tempo di esecuzione. Senza decoratori, era necessario chiamare manualmente le funzioni di incapsulamento, gonfiando il codice.

Soluzione

I decoratori consentono di estrarre aspetti ripetitivi in avvolgimenti separati, migliorando così la leggibilità e il riutilizzo del codice. Con @decorator è possibile aggiungere elegantemente funzionalità a funzioni e metodi:

Esempio di codice:

import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Stampa quanto tempo ha impiegato l'esecuzione

Caratteristiche chiave:

  • Permettono di riutilizzare il codice (principio DRY);
  • Possono essere utilizzati per funzioni, metodi, classi;
  • Consentono di implementare compiti cross-funzionali (registrazione, cache, accesso, profiling) in modo centralizzato;

Domande insidiose.

Possono i decoratori cambiare la firma della funzione avvolta?

Spesso si crede erroneamente che la firma della funzione non cambi con il decoratore. In realtà, senza l'uso del modulo functools.wraps, le informazioni metainformatiche vanno perse, il che può causare errori indesiderati nella documentazione automatica o nell'introspezione.

Esempio di codice:

from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper

Possono i decoratori essere parametrizzati?

Spesso si risponde che non è possibile. In realtà, è possibile creare un decoratore parametrizzato tramite un ulteriore livello di annidamento — una funzione che restituisce un decoratore.

Esempio di codice:

def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")

È possibile applicare più decoratori a una funzione? Qual sarà l'ordine di esecuzione?

Si crede spesso erroneamente che l'ordine non sia importante o che coincida con l'ordine dei decoratori nel codice. In realtà, il decoratore più basso viene applicato per primo, poi il successivo e così via verso l'alto.

Esempio di codice:

def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core

Errori comuni e anti-pattern

  • Ignorare functools.wraps provoca la perdita di informazioni metainformatiche sulla funzione originale;
  • La logica del decoratore non tiene conto delle eccezioni, non è possibile intercettare e gestire gli errori all'interno;
  • Sovrapposizione non ovvia di più decoratori

Esempio dalla vita reale

Caso negativo

La registrazione del tempo di esecuzione è stata aggiunta manualmente a 10 funzioni, per copia e incolla.

Pro:

  • La logica è chiara, il codice è vicino, facile trovare l'errore.

Contro:

  • Difficile da mantenere — sarà necessario modificare decine di punti, se è necessario cambiare il comportamento.
  • Il codice è duplicato, violando il DRY.

Caso positivo

Tutta la logica di temporizzazione è stata estratta in un decoratore, tutte le funzioni in cui è necessaria questa metrica sono state incapsulate con il decoratore @timing_decorator.

Pro:

  • Le modifiche sono fatte centralmente;
  • Il codice è più corto e leggibile.

Contro:

  • Possibile perdita di informazioni sulla firma senza functools.wraps, se non si presta attenzione;
  • I principianti potrebbero avere difficoltà a capire immediatamente il meccanismo di funzionamento dei decoratori.