Introduzione
Quando si parla di decoratori in Python, spesso si pensa a logging, caching o validazione. In realtà, uno degli usi più interessanti e pratici è la registrazione automatica di funzioni in un “catalogo” centrale. Questo approccio è molto utile in applicazioni reali, ad esempio per costruire:
- un sistema di comandi da riga di comando;
- un motore di plugin;
- un router semplice per gestire azioni o eventi;
- un dispatcher di task in un backend.
Il vantaggio è che non devi aggiornare manualmente una lista di funzioni ogni volta che ne aggiungi una nuova: il decoratore si occupa di registrarla automaticamente. Questo è un esempio concreto di metaprogrammazione, cioè codice che modifica o organizza altri elementi del programma in fase di esecuzione.
In questo tutorial vedremo come costruire un piccolo registro di comandi usando un decoratore personalizzato. L’obiettivo è capire non solo come funziona, ma anche perché è utile in progetti reali.
Codice completo
from functools import wraps
# Registro globale dei comandi
COMMANDS = {}
def register_command(name=None):
"""
Decoratore parametrico che registra una funzione in un dizionario globale.
Se name non viene passato, usa il nome della funzione.
"""
def decorator(func):
command_name = name or func.__name__
if command_name in COMMANDS:
raise ValueError(f"Comando già registrato: {command_name}")
COMMANDS[command_name] = func
@wraps(func)
def wrapper(*args, **kwargs):
# Qui potremmo aggiungere logica extra, ma in questo esempio
# il decoratore si occupa soprattutto della registrazione.
return func(*args, **kwargs)
return wrapper
return decorator
@register_command()
def hello():
return "Ciao! Questo è un comando registrato automaticamente."
@register_command("somma")
def add(a, b):
return a + b
@register_command("moltiplica")
def multiply(a, b):
return a * b
def execute_command(command_name, *args):
"""
Cerca il comando nel registro e lo esegue.
"""
if command_name not in COMMANDS:
raise KeyError(f"Comando non trovato: {command_name}")
return COMMANDS[command_name](*args)
if __name__ == "__main__":
print("Comandi disponibili:", list(COMMANDS.keys()))
print(execute_command("hello"))
print(execute_command("somma", 2, 3))
print(execute_command("moltiplica", 4, 5)) Spiegazione
Analizziamo il codice passo per passo, perché qui c’è un aspetto di metaprogrammazione molto interessante: il decoratore non si limita a modificare il comportamento della funzione, ma la registra in una struttura dati esterna.
1. Il dizionario COMMANDS
Il dizionario COMMANDS è il nostro registro centrale. La chiave è il nome del comando, mentre il valore è la funzione associata.
Per esempio:
- "hello" punta alla funzione
hello(); - "somma" punta alla funzione
add(a, b); - "moltiplica" punta alla funzione
multiply(a, b).
2. Il decoratore parametrico register_command
La funzione register_command(name=None) è un decoratore con argomenti. Questo significa che prima riceve i parametri del decoratore e poi restituisce il vero decoratore.
La struttura è questa:
register_command(name=None)riceve il nome opzionale;decorator(func)riceve la funzione da decorare;wrapper(*args, **kwargs)esegue la funzione originale.
3. La registrazione automatica
Dentro decorator(func) calcoliamo il nome del comando:
- se
nameè stato passato, usiamo quello; - altrimenti usiamo
func.__name__.
Poi inseriamo la funzione nel dizionario:
COMMANDS[command_name] = func Questo avviene nel momento in cui Python interpreta la definizione della funzione decorata, non quando la funzione viene chiamata. È qui che entra in gioco la metaprogrammazione: il decoratore agisce sulla struttura del programma durante il caricamento del modulo.
4. Il wrapper e functools.wraps
Il wrapper serve a mantenere la possibilità di aggiungere logiche extra. In questo esempio non fa molto, ma in progetti reali potresti usarlo per:
- controllare permessi;
- misurare tempi di esecuzione;
- tracciare chiamate;
- modificare input o output.
Abbiamo usato @wraps(func) per preservare metadati importanti della funzione originale, come:
__name____doc____module__
È una best practice fondamentale quando scrivi decoratori.
5. Esecuzione dinamica dei comandi
La funzione execute_command cerca il comando nel registro e lo esegue passando gli argomenti ricevuti. Questo approccio è molto utile quando non conosci in anticipo quale funzione dovrà essere eseguita.
In pratica, hai creato una piccola infrastruttura di dispatch dinamico senza ricorrere a catene di if/elif lunghissime.
Best practice
- Usa
functools.wrapssempre che il decoratore ritorni un wrapper. Evita di perdere nome, docstring e altri metadati utili. - Evita registri globali troppo complessi se il progetto cresce molto. Per applicazioni grandi, valuta classi dedicate o moduli separati per il registry.
- Gestisci i conflitti di nome. Nel nostro esempio solleviamo un
ValueErrorse un comando è già presente. È meglio fallire subito che sovrascrivere accidentalmente una funzione. - Separa registrazione ed esecuzione. Il decoratore dovrebbe occuparsi della registrazione; la logica di esecuzione dovrebbe stare altrove.
- Documenta bene i comandi registrati. In sistemi plugin o CLI, una buona introspezione del registry aiuta molto il debug e la manutenzione.
- Attenzione agli effetti collaterali all’import. Siccome la registrazione avviene quando il modulo viene importato, evita logiche pesanti o dipendenze circolari.
Riepilogo
In questo tutorial abbiamo visto un uso molto pratico dei decoratori: registrare automaticamente funzioni in un catalogo centrale. Questo pattern è una forma elegante di metaprogrammazione perché permette al codice di organizzare sé stesso senza configurazioni manuali ripetitive.
I punti chiave da ricordare sono:
- un decoratore può eseguire logica al momento della definizione della funzione;
- i decoratori parametrici sono ideali per configurare il comportamento;
- un registry consente dispatch dinamico e architetture estensibili;
wrapsè essenziale per mantenere pulita l’introspezione delle funzioni.
Questo pattern è semplice, ma scala bene in molti contesti reali: dai piccoli script ai sistemi modulari con plugin o comandi personalizzati.
Approfondisci con risorse ufficiali
- Python Documentation - Decorators: https://docs.python.org/3/glossary.html#term-decorator
- functools.wraps: https://docs.python.org/3/library/functools.html#functools.wraps
- functools - strumenti utili per decoratori e funzioni: https://docs.python.org/3/library/functools.html
- Data model di Python per capire meglio attributi e introspezione: https://docs.python.org/3/reference/datamodel.html
