Подтвердить что ты не робот

Почему мы должны использовать методы __dunder__ вместо операторов при вызове через супер?

Почему мы должны использовать __getitem__ вместо обычного доступа к оператору?

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

Получаем TypeError: 'super' object is not subscriptable.

Вместо этого мы должны использовать super().__getitem__(key), но я никогда не понимал почему - что именно это предотвратило супер-реализацию таким образом, чтобы позволить оператору получить доступ?

Подстраница была просто примером, у меня такой же вопрос для __getattr__, __init__ и т.д.

docs пытается объяснить, почему, но я этого не понимаю.

4b9b3361

Ответ 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__. Чуть загадочный, но меньше, чем бесконечная трассировка стека.

Поэтому причина, по которой супер не работает с магическими методами, заключается в том, что она создает больше проблем, чем она решает.