Почему мы должны использовать методы __dunder__ вместо операторов при вызове через супер?
Почему мы должны использовать __getitem__
вместо обычного доступа к оператору?
class MyDict(dict):
def __getitem__(self, key):
return super()[key]
Получаем TypeError: 'super' object is not subscriptable
.
Вместо этого мы должны использовать super().__getitem__(key)
, но я никогда не понимал почему - что именно это предотвратило супер-реализацию таким образом, чтобы позволить оператору получить доступ?
Подстраница была просто примером, у меня такой же вопрос для __getattr__
, __init__
и т.д.
docs пытается объяснить, почему, но я этого не понимаю.
Ответы
Ответ 1
Отслеживание ошибок CPython issue 805304, "супер экземпляры не поддерживают назначение элемента" , имеет Раймонда Хеттингера подробное объяснение воспринимаемых трудностей.
Причина, по которой это не работает автоматически, заключается в том, что такие методы должны быть определены в классе из-за кэширования методов Python, в то время как прокси-методы найдены во время выполнения.
Он предлагает патч, который даст подмножество этой функции:
+ 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;
+ }
+
поэтому это возможно.
Однако он заключает
Я думал, что это можно оставить в покое и просто документ, в котором супер объекты только делают свою магию явный поиск атрибутов.
В противном случае исправление полностью связано с расчесыванием Python для каждое место, которое напрямую вызывает функции из таблицы слотов, а затем добавление последующего вызова с использованием поиска атрибута, если слот пуст.
Когда дело доходит до таких функций, как repr (obj), я думаю, мы хотим супер-объект, чтобы идентифицировать себя, а не пересылать вызов целевого объекта __repr __().
Аргумент, по-видимому, состоит в том, что если методы __dunder__
проксированы, то либо __repr__
проксируется, либо между ними существует несогласованность. super()
, таким образом, может не захотеть прокси-сервера таких методов, чтобы он не оказался слишком близко к программному эквиваленту сверхъестественной долины.
Ответ 2
Методы Dunder должны быть определены в классе, а не в экземпляре. Для выполнения этой работы super() понадобится реализовать каждый магический метод. Не стоит записывать весь этот код и поддерживать его в актуальном состоянии с определением языка (например, введение матричного умножения в 3.5 создало три новых метода dunder), когда вы можете просто сказать пользователям, чтобы они вручную выписывали методы dunder. Это использует обычный поиск методов, который можно легко эмулировать.
Ответ 3
То, что вы просите, может быть сделано и легко. Например:
class dundersuper(super):
def __add__(self, other):
# this works, because the __getattribute__ method of super is over-ridden to search
# through the given object 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)
Итак, причина, по которой супер работает с магическими методами, не потому, что это невозможно. Причина должна лежать в другом месте. Одна из причин заключается в том, что это нарушит договор равных (==
). То есть равно, среди других критериев, симметрично. Это означает, что если a == b
истинно, то b == a
также должно быть истинным. Это приводит нас в сложную ситуацию, где super(self, CurrentClass) == self
, но self != super(self, CurrentClass)
например.
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
Другая причина заключается в том, что после того, как супер был выполнен поиск его заданного объекта mro, он должен дать себе возможность предоставить запрошенный атрибут - супер объекты по-прежнему являются объектами в своем собственном праве - мы должны быть в состоянии проверить равенство с другими объектами, запрос представления строк и интроспекция работы объекта и класса super. Это создает проблему, если метод dunder доступен для супер объекта, но не для объекта, который представляет собой изменяемый объект. Например:
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
В примере списка функция __iadd__
могла быть проще написана как
def __iadd__(self, other):
return super().__iadd__(other)
С примером кортежа мы попадаем в бесконечную рекурсию, потому что tuple.__iadd__
не существует. Поэтому при поиске атрибута __iadd__
на суперобъекте фактический супер объект проверяется на атрибут __iadd__
(который существует). Мы получаем этот метод и называем его, который снова запускает весь процесс. Если бы мы не написали метод __iadd__
для супер и не использовали super().__iadd__(other)
, тогда этого никогда не было бы. Скорее, мы получили сообщение об ошибке о супер-объекте, не имеющем атрибута __iadd__
. Чуть загадочный, но меньше, чем бесконечная трассировка стека.
Поэтому причина, по которой супер не работает с магическими методами, заключается в том, что она создает больше проблем, чем она решает.