Почему Python 3.x супер() магия?

В Python 3.x super() можно вызывать без аргументов:

class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
>>> B().x()
Hey now

Для выполнения этой работы выполняется некоторая магия времени компиляции, одним из следствий которой является то, что следующий код (который восстанавливает от super до super_) сбой:

super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
>>> B().x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

Почему super() не удается разрешить суперкласс во время выполнения без помощи компилятора? Существуют ли практические ситуации, когда это поведение или его основная причина могут укусить неосторожного программиста?

... и в качестве побочного вопроса: есть ли в Python другие примеры функций, методов и т.д., которые можно сломать, перебирая их на другое имя?

Ответ 1

Новое магическое поведение super() было добавлено во избежание нарушения D.R.Y. (Не повторяйте себя), см. PEP 3135. Чтобы явно назвать класс, ссылаясь на него как на глобальный, также подвержены тем же проблемам перезаписи, которые вы обнаружили с помощью super():

class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

То же самое относится к использованию декораторов классов, в которых декоратор возвращает новый объект, который перепроверяет имя класса:

@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

Магическая ячейка super() __class__ очень хорошо оборачивает эти проблемы, предоставляя вам доступ к исходному объекту класса.

PEP был выпущен Guido, который изначально предполагал super стать ключевым словом, а идея использования ячейки для поиска текущий класс также был его. Конечно, идея сделать это ключевым словом была частью первого черновика PEP.

Однако на самом деле сам Гвидо, который затем , предложив вместо этого, Он ожидал, что использование другого имени для super() может быть проблемой:

Мой патч использует промежуточное решение: он предполагает, что вам нужно __class__всякий раз, когда вы используете переменную с именем 'super'. Таким образом, если вы (глобально) переименуйте super в supper и используйте supper, но не super, это не сработает без аргументов (но он все равно будет работать, если вы передадите его либо __class__ или фактический объект класса); если у вас есть несвязанный переменная с именем super, все будет работать, но метод будет использовать немного более медленный путь вызова, используемый для переменных ячейки.

Итак, в конце концов, именно сам Гидо заявил, что использование ключевого слова super не соответствует действительности, и что предоставление волшебной ячейки __class__ было приемлемым компромиссом.

Я согласен с тем, что магическое, неявное поведение реализации несколько удивительно, но super() является одной из самых неправильно применяемых функций на языке. Просто взгляните на все непринятые super(type(self), self) или super(self.__class__, self), обнаруженных в Интернете; если бы какой-либо из этого кода когда-либо вызывался из производного класса у вас получилось бы исключение бесконечной рекурсии. По меньшей мере упрощенный вызов super() без аргументов позволяет избежать этой проблемы.

Что касается переименованного super_; просто укажите __class__ в своем методе, и он снова будет работать. Ячейка создается, если вы ссылаетесь на имена super или __class__ в вашем методе:

>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
... 
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
... 
>>> B().x()
No flipping