Как вообще работает Python super () в общем случае?
Есть много хороших ресурсов наsuper()
, в том числеэтот отличный пост в блоге, который всплывает много, а также много вопросов о переполнении стека. Однако я чувствую, что все они не могут объяснить, как это работает в самом общем случае (с произвольными графами наследования), а также что происходит под капотом.
Рассмотрим этот базовый пример наследования алмазов:
class A(object):
def foo(self):
print 'A foo'
class B(A):
def foo(self):
print 'B foo before'
super(B, self).foo()
print 'B foo after'
class C(A):
def foo(self):
print 'C foo before'
super(C, self).foo()
print 'C foo after'
class D(B, C):
def foo(self):
print 'D foo before'
super(D, self).foo()
print 'D foo after'
Если вы читаете правила Python для порядка разрешения методов из таких источников, какэтот или посмотрите вверхстраница википедии для линеаризации C3 вы увидите, что MRO должен быть(D, B, C, A, object)
, Это конечно подтверждаетсяD.__mro__
:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
А также
d = D()
d.foo()
печать
D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after
который соответствует MRO. Однако учтите, что вышеsuper(B, self).foo()
вB
на самом деле звонкиC.foo
тогда как вb = B(); b.foo()
было бы просто пойти прямо кA.foo
, Явно используяsuper(B, self).foo()
это не просто ярлык дляA.foo(self)
как иногда учат.
super()
затем, очевидно, знает о предыдущих вызовах перед ним и общем MRO, за которым пытается следовать цепочка. Я вижу два способа, которыми это может быть достигнуто. Во-первых, сделать что-то вроде передачиsuper
сам объект какself
аргумент для следующего метода в цепочке, который будет действовать как исходный объект, но также будет содержать эту информацию. Однако это также, кажется, сломало бы много вещей (super(D, d) is d
неверно) и, немного поэкспериментировав, я вижу, что это не так.
Другой вариант - иметь некоторый глобальный контекст, в котором хранится MRO и текущая позиция в нем. Я представляю алгоритм дляsuper
идет что-то вроде:
super
пример.Когда метод доступен изsuper
Например, найдите его в текущем классе и вызовите его, используя тот же контекст.Однако это не учитывает странные вещи, такие как использование другого базового класса в качестве первого аргумента для вызоваsuper
или даже вызывая другой метод. Я хотел бы знать общий алгоритм для этого. Кроме того, если этот контекст где-то существует, могу ли я его проверить? Могу ли я с этим гадить? Конечно, ужасная идея, но Python, как правило, ожидает, что вы станете взрослым, даже если это не так.
Это также вводит много конструктивных соображений. Если бы я написалB
думать только о его отношении кA
потом потом кто-то еще пишетC
и третье лицо пишетD
мойB.foo()
метод должен вызыватьsuper
таким образом, который совместим сC.foo()
хотя он и не существовал в то время, когда я это писал! Если я хочу, чтобы мой класс был легко расширяемым, я должен учитывать это, но я не уверен, что это сложнее, чем просто убедиться, что все версииfoo
имеют одинаковые подписи. Существует также вопрос о том, когда ставить код до или после вызоваsuper
, даже если это не имеет никакого значения, учитываяB
Только базовые классы.