¿Por qué tenemos que usar los métodos __dunder__ en lugar de los operadores cuando llamamos a través de super?

¿Por qué tenemos que usar __getitem__ lugar del acceso habitual del operador?

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

Obtenemos TypeError: 'super' object is not subscriptable .

En su lugar, debemos usar super().__getitem__(key) , pero nunca entendí completamente por qué: ¿qué es exactamente lo que impidió que se implementara super de una manera que permitiera el acceso del operador?

Subscriptable fue solo un ejemplo, tengo la misma pregunta para __getattr__ , __init__ , etc.

Los doctores intentan explicar por qué, pero no lo entiendo.

El problema del rastreador de errores de CPython, 805304, “las súper instancias no admiten la asignación de elementos”, hace que Raymond Hettinger dé una explicación detallada de las dificultades percibidas.

La razón por la que esto no funciona automáticamente es que tales métodos deben definirse en la clase debido al almacenamiento en caché de los métodos de Python, mientras que los métodos con proxy se encuentran en el tiempo de ejecución.

Él ofrece un parche que le daría un subconjunto de esta funcionalidad:

 + 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; + } + 

por lo que es claramente posible .

Sin embargo, concluye

He estado pensando que este podría quedarse solo y solo documentar que los súper objetos solo hacen su magia en la búsqueda de atributos explícitos.

De lo contrario, corregirlo implica combinar Python para cada lugar que llame directamente a las funciones de la tabla de ranuras, y luego agregar una llamada de seguimiento usando la búsqueda de atributos si la ranura está vacía.

Cuando se trata de funciones como repr (obj), creo que queremos que el súper objeto se identifique en lugar de reenviar la llamada al método __repr __ () del objeto de destino.

El argumento parece ser que si los métodos de __dunder__ son proxy, entonces __repr__ es proxy o si hay una inconsistencia entre ellos. super() tanto, es posible que super() no quiera usar tales métodos para evitar que se acerque demasiado al equivalente del progtwigdor de un valle extraño.

Lo que pides se puede hacer, y fácilmente. Por ejemplo:

 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) 

Entonces la razón por la que el super funciona con métodos mágicos no es porque no sea posible. La razón debe estar en otra parte. Una razón es que hacerlo violaría el contrato de iguales ( == ). Lo que es igual es, entre otros criterios, simétrico. Esto significa que si a == b es verdadero, entonces b == a también debe ser verdadero. Eso nos lleva a una situación difícil, donde super(self, CurrentClass) == self , pero self != super(self, CurrentClass) por ejemplo.

 class dundersuper(super): def __eq__(self, other): return self.__eq__(other) super = dundersuper class A: def self_is_other(self, other): return super() == other # aka 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() # aka 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 

Otra razón es que una vez que Super ha terminado de buscar en el mro del objeto dado, entonces tiene que darse una oportunidad para proporcionar el atributo solicitado (los súper objetos siguen siendo un objeto por derecho propio), debemos poder probar la igualdad con otros objetos, solicite representaciones de cadenas e inspeccione el objeto y la clase con la que está trabajando super. Esto crea un problema si el método dunder está disponible en el súper objeto, pero no en el objeto que representa el objeto mutable. Por ejemplo:

 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 el ejemplo de la lista, la función __iadd__ podría haberse escrito más simplemente como

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

Con el ejemplo de la tupla caemos en una recursión infinita, esto se debe a que la tuple.__iadd__ no existe. Por lo tanto, al buscar el atributo __iadd__ en un súper objeto, el súper objeto real se comprueba para un atributo __iadd__ (que existe). Obtenemos ese método y lo llamamos, lo que inicia todo el proceso nuevamente. Si no hubiéramos escrito un método __iadd__ en super y usamos super().__iadd__(other) , esto nunca hubiera sucedido. Más bien, obtendríamos un mensaje de error sobre un súper objeto que no tiene el atributo __iadd__ . Ligeramente críptico, pero menos que un rastro de stack infinito.

Entonces, la razón por la que super no funciona con los métodos mágicos es que crea más problemas de los que resuelve.

Los métodos de Dunder deben definirse en la clase, no en la instancia. super () necesitaría tener una implementación de cada método mágico para que esto funcione. No vale la pena escribir todo ese código y mantenerlo actualizado con la definición del idioma (por ejemplo, la introducción de la multiplicación de matrices en 3.5 creó tres nuevos métodos de dunder), cuando puede simplemente indicar a los usuarios que escriban los métodos de dunder a mano. Que utiliza el método normal de búsqueda, que puede ser fácilmente emulado.