Как вообще работает 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 идет что-то вроде:

Есть ли в настоящее время контекст, в котором мы работаем? Если нет, создайте тот, который содержит очередь. Получите MRO для аргумента класса, вставьте все элементы, кроме первого, в очередь.Извлеките следующий элемент из очереди MRO текущего контекста, используйте его в качестве текущего класса при созданииsuper пример.Когда метод доступен изsuper Например, найдите его в текущем классе и вызовите его, используя тот же контекст.

Однако это не учитывает странные вещи, такие как использование другого базового класса в качестве первого аргумента для вызоваsuperили даже вызывая другой метод. Я хотел бы знать общий алгоритм для этого. Кроме того, если этот контекст где-то существует, могу ли я его проверить? Могу ли я с этим гадить? Конечно, ужасная идея, но Python, как правило, ожидает, что вы станете взрослым, даже если это не так.

Это также вводит много конструктивных соображений. Если бы я написалB думать только о его отношении кAпотом потом кто-то еще пишетC и третье лицо пишетDмойB.foo() метод должен вызыватьsuper таким образом, который совместим сC.foo() хотя он и не существовал в то время, когда я это писал! Если я хочу, чтобы мой класс был легко расширяемым, я должен учитывать это, но я не уверен, что это сложнее, чем просто убедиться, что все версииfoo имеют одинаковые подписи. Существует также вопрос о том, когда ставить код до или после вызоваsuper, даже если это не имеет никакого значения, учитываяBТолько базовые классы.

Ответы на вопрос(1)

Ваш ответ на вопрос