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

159

В 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 другие примеры функций, методов и т. д., которые можно сломать, связав их с другим именем?

Ноль Пирей
источник
6
Я дам Armin сделать пояснения по этому одному . Это тоже еще один хороший пост
Игры Brainiac
связанные: stackoverflow.com/q/36993577/674039
Вим

Ответы:

217

Новое магическое super()поведение было добавлено, чтобы избежать нарушения принципа СУХОЙ (не повторять себя), см. 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 был запущен Гвидо, который первоначально предполагал 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
Мартейн Питерс
источник
1
Хорошая рецензия. Это все так же ясно, как грязь, однако. Вы говорите, что super () эквивалентна автоматически созданной функции типа def super(of_class=magic __class__)вроде a self.super(); def super(self): return self.__class__?
Чарльз Мерриам
17
@CharlesMerriam: Этот пост не о том, как super()без аргументов работает; это главным образом имеет дело с тем, почему это существует. super()в методе класса эквивалентно super(ReferenceToClassMethodIsBeingDefinedIn, self), где ReferenceToClassMethodIsBeingDefinedInопределяется во время компиляции, присоединяется к методу как именованное замыкание __class__и super()будет искать оба из вызывающего фрейма во время выполнения. Но вам на самом деле не нужно знать все это.
Мартин Питерс
1
@CharlesMerriam: но super()это далеко не автоматическая функция , нет.
Мартин Питерс
1
@ chris.leonard: ключевое предложение: Ячейка создается, если вы используете super () или используете __class__в своем методе . Вы использовали имя superв своей функции. Компилятор видит это и добавляет __class__закрытие.
Мартин Питерс
4
@Alexey: это не достаточно. type(self)дает текущий тип, который не совпадает с типом, для которого определен метод. Таким образом, класс Fooс методом bazнуждается super(Foo, self).baz(), потому что он может быть разделен на подклассы class Ham(Foo):, в какой точке type(self)находится Hamи super(type(self), self).baz()даст вам бесконечный цикл. См. Пост, на который я ссылаюсь в своем ответе: при вызове super () в производном классе могу ли я передать self .__ class__?
Мартин Питерс