Storia della questione:
In Python tutto è un oggetto, e anche qualsiasi funzione è un oggetto. È stato implementato un meccanismo che consente a un'istanza di classe di comportarsi come una funzione. Per questo è stato introdotto un metodo magico speciale call, che consente di rendere gli oggetti invocabili.
Problema:
A volte è necessario passare un oggetto che può eseguire azioni quando viene invocato, ad esempio funzioni con stato (funzioni stateful), chiusure, oggetti gestori di eventi, ecc. Lo sviluppo dell'architettura richiede comprensione su come implementare correttamente tale funzionalità.
Soluzione:
Implementando il metodo call nella classe, è possibile rendere la sua istanza invocabile "come una funzione". Questo consente di unire le capacità delle classi (incapsulamento dello stato, ereditarietà, metodi) e delle funzioni (invocabilità). Questo approccio viene utilizzato per creare oggetti comando, gestori complessi, wrapper, ecc.
Esempio di codice:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Stampa 15
Caratteristiche chiave:
L'oggetto con call eredita le proprietà di una normale funzione — ad esempio, name e doc?
No, l'oggetto con il metodo call avrà l'attributo name assente (o lo prenderà dalla classe). I metadati delle funzioni non vengono mantenuti.
L'oggetto con call è una vera funzione?
No, è un'istanza di classe, non una funzione. Implementa solo il comportamento "invocabile". Ad esempio, il confronto con una funzione tramite isinstance(obj, types.FunctionType) darà False.
È possibile applicare un decoratore pensato per una funzione a un oggetto con call?
Di solito, questi decoratori si aspettano proprio una funzione, non un oggetto (ad esempio, functools.lru_cache). L'uso può portare a errori o non funzionare affatto.
Pro:
Caso negativo: In un progetto è stato implementato un logger tramite una classe con call, per conservare le impostazioni (livello, nome del file). Tuttavia, si è dimenticato che le funzioni gestori di segnali si aspettano proprio una funzione, e si sono verificati errori durante la registrazione del gestore (object is not a function).
Pro: configurazione flessibile del logger. Contro: incompatibilità con le interfacce attese.
Caso positivo: In un altro progetto è stata utilizzata una classe con call per creare complessi funzioni-decoratori, mantenendo i parametri, il che ha semplificato il testing.
Pro: estensibilità, comodità. Contro: più codice rispetto a una funzione o lambda.