2012-04-29 14 views
13

Spero che qualcuno può rispondere a questa che ha una buona comprensione profonda di Python :)metodi speciali imperative su un'istanza

Si consideri il seguente codice:

>>> class A(object): 
...  pass 
... 
>>> def __repr__(self): 
...  return "A" 
... 
>>> from types import MethodType 
>>> a = A() 
>>> a 
<__main__.A object at 0x00AC6990> 
>>> repr(a) 
'<__main__.A object at 0x00AC6990>' 
>>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__)) 
>>> a 
<__main__.A object at 0x00AC6990> 
>>> repr(a) 
'<__main__.A object at 0x00AC6990>' 
>>> 

Si noti come repr (a) non cede il risultato atteso di "A"? Voglio sapere perché questo è il caso e se c'è un modo per raggiungere questo obiettivo ...

ho contrasto, il seguente esempio funziona comunque (Forse perché non stiamo cercando di eseguire l'override un metodo speciale?):

>>> class A(object): 
...  def foo(self): 
...    return "foo" 
... 
>>> def bar(self): 
...  return "bar" 
... 
>>> from types import MethodType 
>>> a = A() 
>>> a.foo() 
'foo' 
>>> setattr(a, "foo", MethodType(bar, a, a.__class__)) 
>>> a.foo() 
'bar' 
>>> 
+0

Probabilmente otterrai una risposta molto più illuminante se dici quello che stai cercando di ottenere. – mattmanser

+1

Per le classi di nuovo stile, i metodi speciali vengono cercati sulla classe piuttosto che sull'istanza. Vedere la mia risposta di seguito per ulteriori dettagli e per alcuni soluzioni alternative. –

+0

Non saprei descrivere cosa voglio fare in dettaglio. Scusate. Ma in breve, sto cercando di modellare un prototipo di modello OO in cui posso fare operatori come: World = Object(). Clone(). Mixin (World); Dove World è una classe con una raccolta di metodi che sovrascrivono/sostituiscono uno nell'istanza Object.clone(). –

risposta

15

Python non chiama i metodi speciali, quelli con nome circondato da __ sull'istanza, ma solo sulla classe, apparentemente per migliorare le prestazioni. Quindi non c'è modo di sovrascrivere __repr__() direttamente su un'istanza e farlo funzionare. Invece, è necessario fare qualcosa di simile in modo da:

class A(object): 
    def __repr__(self): 
     return self._repr() 
    def _repr(self): 
     return object.__repr__(self) 

Ora è possibile ignorare __repr__() su un'istanza sostituendo _repr().

+0

"Python non chiama i metodi speciali, quelli con nome circondato da __ sull'istanza, ma solo sulla classe, apparentemente per migliorare le prestazioni" <- Hai un riferimento a questo? –

+1

Vedere [this] (http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes). Le regole sono leggermente rilassate sulle classi vecchio stile, ma dovresti usare classi di nuovo stile, e in Python 3 sono comunque l'unico tipo. – kindall

+5

Non è principalmente per migliorare le prestazioni. Come dicono i documenti, "La logica alla base di questo comportamento sta in una serie di metodi speciali come' __hash __() 'e' __repr __() 'che sono implementati da tutti gli oggetti, inclusi gli oggetti di tipo .Se la ricerca implicita di questi metodi è usata il processo di ricerca convenzionale, fallirebbero se invocati sull'oggetto tipo stesso ... "(Tuttavia, la logica per' __getattribute__' _also_ essendo performance _is_ skippable.) – abarnert

3

la ragione di questo è metodi speciali (__x__()) sono definiti per la classe, non l'istanza.

Questo ha senso quando si pensa a __new__() - sarebbe impossibile chiamarlo su un'istanza poiché l'istanza non esiste quando viene chiamata.

Così si può fare questo sulla classe nel suo insieme se si vuole:

>>> A.__repr__ = __repr__ 
>>> a 
A 

o su una singola istanza, come in kindall's answer. (Nota: qui c'è molta somiglianza, ma ho pensato che i miei esempi fossero sufficienti per postare anche questo).

3

Per le nuove classi di stile, Python utilizza una ricerca metodo speciale che ignora le istanze. Ecco un estratto dal the source:

1164 /* Internal routines to do a method lookup in the type 
    1165 without looking in the instance dictionary 
    1166 (so we can't use PyObject_GetAttr) but still binding 
    1167 it to the instance. The arguments are the object, 
    1168 the method name as a C string, and the address of a 
    1169 static variable used to cache the interned Python string. 
    1170 
    1171 Two variants: 
    1172 
    1173 - lookup_maybe() returns NULL without raising an exception 
    1174  when the _PyType_Lookup() call fails; 
    1175 
    1176 - lookup_method() always raises an exception upon errors. 
    1177 
    1178 - _PyObject_LookupSpecial() exported for the benefit of other places. 
    1179 */ 

è possibile modificare a una classe vecchio stile (non ereditare da oggetto) oppure è possibile aggiungere metodi dispatcher alla classe (metodi che le ricerche successivo indietro esempio). Per un esempio di metodi dispatcher istanza, vedere la ricetta a http://code.activestate.com/recipes/578091

+0

AFAIK se dovessi passare all'utilizzo di classi Old-Style, questo non funzionerebbe con Python 3? –

7

Come spiegato in Special Method Lookup:

per classi personalizzate, invocazioni implicite di metodi speciali sono garantiti solo per funzionare correttamente se definito dal tipo di un oggetto, non nel dizionario istanza dell'oggetto ... Oltre a bypassando qualsiasi istanza attributi nell'interesse della correttezza, implicita speciale metodo di ricerca in genere ignora anche il metodo __getattribute__() anche della meta-classe dell'oggetto

(La parte che ho ritagliato spiega la logica alla base di questo, se sei interessato a questo.)

Python non documenta esattamente quando un'implementazione deve o non deve cercare il metodo sul tipo; tutto ciò che documenta è, in effetti, che le implementazioni possono o meno guardare l'istanza per ricerche di metodi speciali, quindi non dovresti contare su nessuno dei due.

Come si può intuire dai risultati del test, nell'implementazione CPython, __repr__ è una delle funzioni cercate sul tipo.


Le cose sono leggermente diverse in 2.x, soprattutto a causa della presenza di classi classici, ma finché si sta solo creando classi di nuovo stile si può pensare a loro come la stessa.


Il motivo più comune per cui la gente vuole fare questo è quello di eseguire il patch delle diverse istanze di un oggetto per fare cose diverse. Non puoi farlo con metodi speciali, quindi ... cosa puoi fare? C'è una soluzione pulita e una soluzione hacky.

La soluzione pulita consiste nell'implementare un metodo speciale sulla classe che richiama semplicemente un metodo regolare nell'istanza. Quindi puoi eseguire il patch delle scimmie su un metodo normale su ciascuna istanza. Ad esempio:

class C(object): 
    def __repr__(self): 
     return getattr(self, '_repr')() 
    def _repr(self): 
     return 'Boring: {}'.format(object.__repr__(self)) 

c = C() 
def c_repr(self): 
    return "It's-a me, c_repr: {}".format(object.__repr__(self)) 
c._repr = c_repr.__get__(c) 

La soluzione hacky è quella di creare una nuova sottoclasse al volo e riclassificare l'oggetto. Sospetto che chiunque abbia una situazione in cui questa sia una buona idea saprà come implementarla da quella frase, e chiunque non sappia come farlo non dovrebbe tentare, quindi lascerò perdere.

+0

Penso che per funzionare su 2.x, potrebbe essere necessario chiamare '__get __ (c, type (c))', ma non mi ricordo per certo, e non ho un interprete 2.x funzionante su questa macchina ... Forse sarebbe meglio usare 'types.MethodType' invece? – abarnert