Introduzione
Uno degli usi più pratici dei decoratori in Python è costruire un sistema di plugin automatico. In molte applicazioni reali, infatti, serve registrare funzioni o classi in modo dinamico, così da poterle richiamare tramite un nome, un comando o un identificatore senza dover modificare manualmente un elenco centrale ogni volta che si aggiunge una nuova funzionalità.
Questo approccio è molto utile in progetti come motori di regole, CLI, pipeline di elaborazione dati, sistemi di eventi, chatbot e framework modulari. La metaprogrammazione entra in gioco perché il codice non si limita a eseguire istruzioni, ma modifica o arricchisce il comportamento di altri oggetti durante il runtime.
In questo tutorial vedremo come creare un piccolo registry di plugin usando un decoratore. L’obiettivo è ottenere codice pulito, estendibile e facile da mantenere.
Codice completo
from functools import wraps
class PluginRegistry:
"""
Registro centralizzato dei plugin.
Ogni plugin viene salvato con una chiave testuale.
"""
def __init__(self):
self._plugins = {}
def register(self, name=None):
"""
Decoratore parametrico per registrare una funzione.
Se name non viene passato, usa il nome della funzione.
"""
def decorator(func):
plugin_name = name or func.__name__
if plugin_name in self._plugins:
raise ValueError(f"Plugin già registrato: {plugin_name}")
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
self._plugins[plugin_name] = wrapper
return wrapper
return decorator
def get(self, name):
"""Recupera un plugin dal registro."""
try:
return self._plugins[name]
except KeyError:
raise KeyError(f"Plugin non trovato: {name}")
def list_plugins(self):
"""Restituisce i nomi dei plugin registrati."""
return list(self._plugins.keys())
def run(self, name, *args, **kwargs):
"""Esegue un plugin registrato."""
plugin = self.get(name)
return plugin(*args, **kwargs)
# Istanza globale del registro
registry = PluginRegistry()
@registry.register()
def greet_user(username):
return f"Ciao, {username}!"
@registry.register(name="sum_numbers")
def add(a, b):
return a + b
@registry.register(name="format_title")
def make_title(text):
return text.strip().upper()
if __name__ == "__main__":
print("Plugin disponibili:", registry.list_plugins())
print(registry.run("greet_user", "Luca"))
print(registry.run("sum_numbers", 10, 32))
print(registry.run("format_title", " decoratori in python ")) Spiegazione
Il codice mostra un pattern molto comune: un oggetto registry che conserva riferimenti a funzioni registrate automaticamente tramite decoratore.
1. La classe PluginRegistry
La classe contiene un dizionario privato _plugins, dove la chiave è il nome del plugin e il valore è la funzione stessa. Questo è un esempio di metaprogrammazione perché stiamo costruendo una struttura che organizza il comportamento del programma in modo dinamico.
2. Il decoratore register()
Il metodo register restituisce un decoratore. È un decoratore parametrico, perché può ricevere un nome esplicito:
- se scriviamo @registry.register(), il plugin prende il nome della funzione;
- se scriviamo @registry.register(name="qualcosa"), usiamo un nome personalizzato.
Questa flessibilità è utile quando il nome della funzione non coincide con il nome che vogliamo esporre all’esterno.
3. wraps(func)
Il decoratore @wraps(func) preserva metadati importanti della funzione originale, come __name__ e __doc__. Senza wraps, la funzione decorata perderebbe identità e introspezione, rendendo più difficile il debug e la documentazione automatica.
4. Il metodo run()
Il metodo run è un’interfaccia comoda: recupera il plugin dal registro e lo esegue con gli argomenti passati. In questo modo il resto dell’applicazione non deve conoscere l’implementazione interna della funzione, ma solo il suo nome registrato.
5. Esempio pratico
Nel blocco finale registriamo tre funzioni:
- greet_user per generare un saluto;
- add, registrata come sum_numbers, per sommare due valori;
- make_title, registrata come format_title, per normalizzare una stringa.
Quando eseguiamo il programma, il registry contiene già le funzioni pronte all’uso. Non serve alcun passaggio manuale di registrazione: avviene tutto al momento della definizione della funzione.
Best practice
Quando usi decoratori e metaprogrammazione per costruire registri automatici, conviene seguire alcune regole pratiche:
- Usa functools.wraps per non perdere metadati e migliorare la leggibilità del codice.
- Evita registri globali troppo nascosti: in progetti grandi è meglio incapsulare il registry in un modulo ben definito o in una classe dedicata.
- Gestisci i duplicati: se due funzioni usano lo stesso nome, solleva un errore chiaro invece di sovrascrivere silenziosamente.
- Separa registrazione ed esecuzione: il decoratore dovrebbe occuparsi della registrazione, mentre un metodo separato dovrebbe eseguire il plugin.
- Documenta i nomi pubblici: se usi un nome personalizzato nel registro, spiega chiaramente come viene scelto e perché.
- Non abusare della magia: la metaprogrammazione è potente, ma se il flusso diventa troppo implicito il codice può risultare difficile da mantenere.
Inoltre, se il tuo progetto cresce, puoi estendere questo pattern per registrare anche classi, handler di eventi, trasformazioni dati o comandi CLI. In questi casi il concetto resta lo stesso: il decoratore aggiunge un comportamento laterale, cioè la registrazione, senza cambiare la logica principale della funzione.
Riepilogo
In questo tutorial abbiamo visto come usare decoratori e metaprogrammazione per costruire un sistema di plugin automatico. Questo approccio è utile quando vuoi rendere un’applicazione modulare e facilmente estendibile.
- Il decoratore intercetta la definizione della funzione.
- La funzione viene salvata in un registro con un nome scelto automaticamente o manualmente.
- Il resto del programma può richiamarla in modo dinamico tramite una chiave.
Il risultato è un codice più ordinato, con meno accoppiamento tra componenti e maggiore facilità di estensione.
Approfondisci con risorse ufficiali
- Python Documentation - functools: per approfondire
wrapse gli strumenti utili ai decoratori. - Python Documentation - classes: per capire meglio il comportamento degli oggetti e l’organizzazione di registry e manager.
- Python Documentation - data model: utile per esplorare il funzionamento interno degli oggetti, delle callable e dell’introspezione.
- PEP 318: la proposta storica che introduce la sintassi dei decoratori in Python.
- PEP 362: utile per comprendere la signature delle funzioni e l’introspezione avanzata.
