Perché non si aggiunge dynamicmente un metodo `__call__` a un’istanza?

In entrambi Python 2 e Python 3 il codice:

class Foo(object): pass f = Foo() f.__call__ = lambda *args : args f(1, 2, 3) 

ritorna come errore L’ Foo object is not callable . Perché succede?

PS: con le classi vecchio stile funziona come previsto.

PPS: questo comportamento è inteso (vedi risposta accettata). Come soluzione, è ansible definire un __call__ a livello di class che __call__ semplicemente a un altro membro e imposta questo membro “normale” a un’implementazione __call__ per-instance.

I metodi double-underscore sono sempre ricercati nella class, mai l’istanza. Vedi Ricerca metodo speciale per classi di nuovo stile :

Per le classi di nuovo stile, le invocazioni implicite di metodi speciali sono garantite per funzionare correttamente solo se definite sul tipo di un object, non nel dizionario di istanza dell’object.

Questo perché il tipo potrebbe aver bisogno di supportare la stessa operazione (nel qual caso il metodo speciale viene cercato sul metatype).

Ad esempio, le classi sono callable (è così che si produce un’istanza), ma se Python __call__ metodo __call__ sull’object reale , non si può mai farlo su classi che implementano __call__ per le loro istanze. ClassObject() diventerebbe ClassObject.__call__() che fallirebbe perché il parametro self non viene passato al metodo unbound. Quindi, invece, type(ClassObject).__call__(ClassObject) , e chiamando instance() traduce in type(instance).__call__(instance) .

Per ovviare a questo, è ansible aggiungere un metodo __call__ alla class che controlla un attributo __call__ sulla class e, se presente, lo chiama.

Nelle nuove classi di stile (default 3.x ) e ereditate dall’object in 2.x i metodi di intercettazione dell’attributo __getattr__ e __getattribute__ non sono più chiamati per operazioni integrate su metodi dunder overload di istanze e invece la ricerca inizia nella class.

La logica alla base di questo, comporta una tecnicità introdotta dalla presenza di MetaClass.

Poiché le classi sono istanze di metaclassi e poiché le metaclassi potrebbero definire determinate operazioni che agiscono sulle classi, ignorare la class (che in questo caso può essere considerata instance ) ha senso; è necessario invocare il metodo definito nel metaclass che è definito per elaborare le classi ( cls come primo argomento). Se è stata utilizzata la ricerca dell’istanza, la ricerca utilizzerà il metodo di class definito per le istanze di tale class ( self ).

Un altro (motivo controverso ) comporta l’ ottimizzazione : poiché le operazioni incorporate su istanze vengono solitamente invocate molto frequentemente, saltando del tutto la ricerca dell’istanza e andando direttamente alla class ci risparmia un po ‘di tempo. [ Source Lutz, Learning Python, 5th Edition ]


L’ area principale in cui ciò potrebbe essere di disagio è quando si creano oggetti proxy che sovraccaricano __getattr__ e __getattribute__ con l’intento di inoltrare le chiamate all’object interno incorporato. Poiché l’invocazione integrata salterà del tutto l’istanza, non verrà catturata e un effetto non verrà inoltrato all’object incorporato.

Un facile, ma noioso, work-around per questo è quello di sovraccaricare effettivamente tutti i dunder che si desidera intercettare nell’object proxy. Quindi in questi dumper sovraccarichi è ansible debind la chiamata all’object interno come richiesto.


La soluzione più semplice a cui riesco a pensare è impostare l’attributo sulla class Foo con setattr :

 setattr(Foo, '__call__', lambda *args: print(args)) f(1, 2, 3) (<__main__.Foo object at 0x7f40640faa90>, 1, 2, 3)