2015-11-20 26 views
12

In entrambi Python 2 e Python 3 del codice:Perché non si aggiunge dinamicamente un metodo `__call__` a un'istanza?

class Foo(object): 
    pass 

f = Foo() 
f.__call__ = lambda *args : args 

f(1, 2, 3) 

restituisce come errore Foo object is not callable. Perché succede?

PS: con le classi vecchio stile funziona come previsto.

PPS: questo comportamento è previsto (vedere risposta accettata). Come soluzione, è possibile definire un __call__ a livello di classe che inoltra semplicemente a un altro membro e imposta questo membro "normale" a un'implementazione __call__ peristanza.

risposta

13

I metodi double-underscore sono sempre ricercati nella classe, mai nell'istanza. Vedere Special method lookup for new-style classes:

Per classi di nuovo stile, invocazioni implicite di metodi speciali sono garantite solo per funzionare correttamente se definito dal tipo di un oggetto, non nel dizionario istanza dell'oggetto.

Questo perché il tipo potrebbe aver bisogno di sostenere la stessa operazione (in questo caso il metodo speciale viene cercato sulla metatipo).

Per esempio, le classi sono callable (è così che si produce un esempio), ma se Python alzò il metodo __call__ sull'oggetto reale, allora si potrebbe mai farlo in classi che implementano __call__ per le loro istanze. ClassObject() diventerebbe ClassObject.__call__() che non riuscirebbe perché il parametro self non viene passato al metodo non associato. Quindi, viene utilizzato lo type(ClassObject).__call__(ClassObject) e il numero instance() viene convertito in type(instance).__call__(instance).

+0

Per definire una per ogni istanza '__call__' implementazione Attualmente sto usando una classe -level '__call__' che inoltra a un membro normale. Tuttavia, sembra una brutta soluzione ... – 6502

+0

@ 6502: questa è la soluzione consigliata. Se hai davvero bisogno di delegare a un callable per istanza, allora farlo in modo esplicito in un '__call__' a livello di classe è il modo migliore per farlo. –

+0

@MartijnPieters Quale sarebbe un modo per aggirare tutti gli operatori ('__add__',' __rtruediv__', ecc.) In un intero archivio oggetti all'interno del proxy? Mi piacerebbe non implementare tutti i metodi manualmente se possibile. – danijar

3

Nel nuove classi di stile (di default) e 3.x ereditati da oggetto in 2.x metodi attributo intercettazione __getattr__ e __getattribute__are no longer called for built-in operations su Dunder sovraccarico metodi di istanze e invece la ricerca inizia con la classe.

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 possono definire determinate operazioni che agiscono sulle classi, ignorare la classe (che in questo caso può essere considerata un instance) ha senso; è necessario richiamare il metodo definito nel metaclass definito per elaborare le classi (cls come primo argomento). Se è stata utilizzata la ricerca dell'istanza, la ricerca utilizza il metodo di classe definito per le istanze di tale classe (self).

Altro (contestato ragione) coinvolge ottimizzazione: Dal momento che le operazioni di incasso sui casi di solito sono invocati molto spesso, saltando l'istanza look-up del tutto e andare direttamente alla classe ci salva un po 'di tempo.[Fonte Lutz, Imparare Python, 5th Edition]


Il area principale in cui questo potrebbe essere di inconveniente è durante la creazione di oggetti proxy che sovraccaricano __getattr__ e __getattribute__ con l'intenzione di inoltrare le chiamate al incorporato interna oggetto. Poiché l'invocazione integrata salterà del tutto l'istanza, non verrà catturata e un effetto non verrà inoltrato all'oggetto incorporato.

Un facile, ma noioso, work-around per questo è di sovraccaricare effettivamente tutti i dunder che si desidera intercettare nell'oggetto proxy. Quindi in questi dumper sovraccarichi è possibile delegare la chiamata all'oggetto interno come richiesto.


Il metodo più semplice work-around che posso pensare è impostare l'attributo della classe Foo con setattr:

setattr(Foo, '__call__', lambda *args: print(args)) 

f(1, 2, 3) 
(<__main__.Foo object at 0x7f40640faa90>, 1, 2, 3) 
+0

Qui stai confondendo le cose. Python ignora '__getattribute__' in alcuni casi come un'ottimizzazione. Ma cercare metodi speciali sulla classe non è una scelta di ottimizzazione; è necessario per supportare correttamente i metodi speciali che agiscono * sul tipo *. Confronta 'hash (int)' con 'hash (1)', per esempio, entrambi devono essere supportati. –

+0

L'ho aggiunto come seconda ragione; ma questo * non * potrebbe essere considerato anche per l'ottimizzazione poiché nei casi in cui abbiamo ** molte ** istanze di una determinata classe saltiamo la ricerca '__dict__' nel nuovo modello? –

+0

Questa ricerca è a buon mercato e quando si chiama un metodo speciale su un'istanza, * c'è solo un'istanza *. –