На super()
очень много ресурсов, в том числе this отличное сообщение в блоге, которое всплывает много, а также много вопросов о переполнении стека. Однако я чувствую, что все они перестают объяснять, как это работает в самом общем случае (с произвольными графиками наследования), а также о том, что происходит под капотом.Как работает super() на Python в общем случае?
Рассмотрим простой пример наследования алмазов:
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 для метода разрешения порядка из источников, таких как this или посмотреть вверх wikipedia page для C3 линеаризация, вы увидите, что MRO должен be (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
.
«Я не уверен, что это сложнее, чем просто убедиться, что все версии foo имеют одинаковые подписи», что является требованием использования супер в целом (хотя вы можете любопытно обойти его с помощью kwargs) –
Вам нужно либо иметь идентичные подписи или использовать '* args, ** kwargs', чтобы вытереть все остальное, что проходит. Первый аргумент 'super' - это класс *, над которым * он должен искать метод - в общем, вы хотите тот, который находится выше текущего, и, следовательно,' super (ThisClass, self) '. * "' super (B, self) .foo() 'не просто ярлык для' A.foo (self) '" * - no, он вызывает следующую реализацию 'foo' в MRO после' B', привязки это «сам». Вы посмотрели ** связанные вопросы ** на боковой панели, некоторые из них кажутся релевантными. – jonrsharpe
@jonrsharpe - Я рассмотрел связанные вопросы, но, как я уже сказал, они действительно рассматривают только упрощенные версии, а не общий случай. Недавно я узнал, что 'super (B, self) .foo()' не является ярлыком для 'A.foo (self)', поскольку я больше учусь об этом, но его часто преподают именно таким образом , Конечно, это не имеет значения в большинстве случаев только с единственным наследованием, но это означает, что в общем случае нецелесообразно использовать 'super (cls, self) .__ init__' с явными аргументами, поскольку он возвращается к' объект' в конечном счете. – JaredL