2014-09-19 12 views
33

Perché dobbiamo utilizzare __getitem__ anziché il solito accesso all'operatore?Perché dobbiamo usare i metodi __dunder__ anziché gli operatori quando chiamano tramite super?

class MyDict(dict): 
    def __getitem__(self, key): 
     return super()[key] 

Otteniamo TypeError: 'super' object is not subscriptable.

Invece dobbiamo usare super().__getitem__(key), ma non ho mai capito del tutto perché - cosa esattamente impedisce l'implementazione di super in un modo che consenta l'accesso all'operatore?

Subscriptable era solo un esempio, ho la stessa domanda per __getattr__, __init__, ecc

La docs tentativo di spiegare perché, ma io non lo capisco.

+0

http://stackoverflow.com/questions/12047847/super-object-not-calling-getattr –

+0

Anche utile: https://docs.python.org/3/reference/datamodel.html#special-method- look – wim

risposta

18

L'inseguitore di bug di CPython issue 805304, "super instances don't support item assignment", ha fornito a Raymond Hettinger una spiegazione dettagliata delle difficoltà percepite.

Il motivo per cui questo non funziona automaticamente è che tali metodi devono essere definiti sulla classe a causa della memorizzazione nella cache dei metodi di Python, mentre i metodi proxy sono reperibili in fase di runtime.

Egli offre a patch che possa dare un sottoinsieme di questa funzionalità:

+ if (o->ob_type == &PySuper_Type) { 
+  PyObject *result; 
+  result = PyObject_CallMethod(o, "__setitem__", "(OO)", key, value); 
+  if (result == NULL) 
+   return -1; 
+  Py_DECREF(result); 
+  return 0; 
+ } 
+ 

quindi è chiaramente possibile.

Tuttavia, conclude

Ho pensato che questo potrebbe essere lasciato solo e proprio documento che gli oggetti super-solo fanno la loro magia su di esplicita ricerca attributo.

Altrimenti, fissandolo completamente coinvolge pettinatura pitone ogni posto che chiama direttamente funzioni dalla tabella slot, e quindi aggiungendo una chiamata follow utilizzando la ricerca attributo se lo slot è vuoto.

Quando si tratta di funzioni come repr (obj), penso che vogliamo l'oggetto super per identificare se stesso, piuttosto che inoltrare la chiamata al dell'oggetto target() metodo __repr __.

L'argomento sembra essere che se __dunder__ metodi sono inoltrate, allora o __repr__ viene inoltrata o v'è una contraddizione tra loro. super(), quindi, potrebbe non voler delegare tali metodi per evitare che si avvicini troppo all'equivalente del programmatore di una valle misteriosa.

3

I metodi Dunder devono essere definiti sulla classe, non sull'istanza. super() avrebbe bisogno di avere una implementazione di ogni metodo magico per farlo funzionare. Non vale la pena scrivere tutto quel codice e tenerlo aggiornato con la definizione della lingua (ad esempio l'introduzione della moltiplicazione di matrici nella 3.5 ha creato tre nuovi metodi dunder), quando si può semplicemente dire agli utenti di scrivere a mano i metodi dunder. Ciò utilizza la normale ricerca del metodo, che può essere facilmente emulata.

+2

FWIW, questo sembra essere un sottoinsieme della mia risposta (ora cancellata automaticamente). La taglia dice "la risposta attuale non è convincente per me" quindi probabilmente non lo sarà neanche questa volta. – Veedrac

3

Quello che chiedi può essere fatto, e facilmente.Per esempio:

class dundersuper(super): 
    def __add__(self, other): 
     # this works, because the __getattribute__ method of super is over-ridden to search 
     # through the given object's mro instead of super's. 
     return self.__add__(other) 

super = dundersuper 

class MyInt(int): 
    def __add__(self, other): 
     return MyInt(super() + other) 

i = MyInt(0) 
assert type(i + 1) is MyInt 
assert i + 1 == MyInt(1) 

Quindi il motivo per cui super funziona con i metodi magici non è perché non è possibile. La ragione deve essere altrove. Uno dei motivi è che ciò violerebbe il contratto di pari diritti (==). Questo è uguale, tra gli altri criteri, simmetrico. Ciò significa che se a == b è vero, allora anche b == a deve essere vero. Questo ci porta in una situazione difficile, dove super(self, CurrentClass) == self, ma self != super(self, CurrentClass) ad es.

class dundersuper(super): 
    def __eq__(self, other): 
     return self.__eq__(other) 

super = dundersuper 

class A: 
    def self_is_other(self, other): 
     return super() == other # a.k.a. object.__eq__(self, other) or self is other 
    def __eq__(self, other): 
     """equal if both of type A""" 
     return A is type(self) and A is type(other) 

class B: 
    def self_is_other(self, other): 
     return other == super() # a.k.a object.__eq__(other, super()), ie. False 
    def __eq__(self, other): 
     return B is type(self) and B is type(other) 

assert A() == A() 
a = A() 
assert a.self_is_other(a) 
assert B() == B() 
b = B() 
assert b.self_is_other(b) # assertion fails 

Un altro motivo è che una volta eccellente è fatto searching è dato MRO dell'oggetto, esso deve poi darsi la possibilità di fornire l'attributo richiesto - oggetti eccellenti sono ancora un oggetto a sé stanti - dovremmo essere in grado di testare l'uguaglianza con altri oggetti, chiedere rappresentazioni di stringhe e introspezionare l'oggetto e la classe super con cui sta lavorando. Questo crea un problema se il metodo dunder è disponibile sull'oggetto super, ma non sull'oggetto rappresentato dall'oggetto mutabile. Per esempio:

class dundersuper(super): 
    def __add__(self, other): 
     return self.__add__(other) 
    def __iadd__(self, other): 
     return self.__iadd__(other) 

super = dundersuper 

class MyDoubleList(list): 
    """Working, but clunky example.""" 
    def __add__(self, other): 
     return MyDoubleList(super() + 2 * other) 
    def __iadd__(self, other): 
     s = super() 
     s += 2 * other # can't assign to the result of a function, so we must assign 
     # the super object to a local variable first 
     return s 

class MyDoubleTuple(tuple): 
    """Broken example -- iadd creates infinite recursion""" 
    def __add__(self, other): 
     return MyDoubleTuple(super() + 2 * other) 
    def __iadd__(self, other): 
     s = super() 
     s += 2 * other 
     return s 

Con l'esempio lista la funzione __iadd__ avrebbe potuto essere più semplicemente scritto come

def __iadd__(self, other): 
    return super().__iadd__(other) 

Con l'esempio tupla cadiamo in ricorsione infinita, questo è perché tuple.__iadd__ non esiste. Pertanto, quando si ricerca l'attributo __iadd__ su un superoggetto, l'oggetto super effettivo viene controllato per un attributo __iadd__ (che esiste). Otteniamo quel metodo e lo chiamiamo, che avvia di nuovo l'intero processo. Se non avessimo scritto un metodo __iadd__ su super e utilizzato super().__iadd__(other), questo non sarebbe mai successo. Piuttosto, riceviamo un messaggio di errore su un superoggetto che non ha l'attributo __iadd__. Leggermente criptico, ma meno di una traccia di stack infinita.

Quindi la ragione super non funziona con i metodi magici è che crea più problemi di quanti ne risolva.

Problemi correlati