Ответ 1
Резюме Exec:
Super выполняет только один метод, основанный на иерархии классов __mro__
. Если вы хотите выполнить несколько методов одним и тем же именем, ваши родительские классы должны записываться совместно, чтобы сделать это (вызывая super
неявно или явно), или вам нужно перебрать __bases__
или __mro__
значения дочерних классов.
Задача super
- делегировать часть или весь вызов метода некоторому существующему методу в дереве предков классов. Делегация может пойти далеко за пределы классов, которые вы контролируете. Дефинированное имя метода должно существовать в группе базовых классов.
Метод, представленный ниже, используя __bases__
с try/except
, ближе всего подходит для полного ответа на ваш вопрос о том, как вызвать каждый родительский метод с тем же именем.
super
полезен в ситуации, когда вы хотите вызвать один из своих родительских методов, но вы не знаете, какой из родителей:
class Parent1(object):
pass
class Parent2(object):
# if Parent 2 had on_start - it would be called instead
# because Parent 2 is left of Parent 3 in definition of Child class
pass
class Parent3(object):
def on_start(self):
print('the ONLY class that has on_start')
class Child(Parent1, Parent2, Parent3):
def on_start(self):
super(Child, self).on_start()
В этом случае Child
имеет трех непосредственных родителей. Только один, Parent3, имеет метод on_start
. Вызов super
разрешает, что только Parent3
имеет on_start
, и это метод, который вызывается.
Если Child
наследуется от более чем одного класса, который имеет метод on_start
, порядок разрешен слева направо (как указано в определении класса) и снизу вверх (как логическое наследование). Вызывается только один из методов, а другие методы с тем же именем в иерархии классов заменяются.
Итак, чаще:
class GreatGrandParent(object):
pass
class GrandParent(GreatGrandParent):
def on_start(self):
print('the ONLY class that has on_start')
class Parent(GrandParent):
# if Parent had on_start, it would be used instead
pass
class Child(Parent):
def on_start(self):
super(Child, self).on_start()
Если вы хотите вызвать несколько методов родителей по имени метода, вы можете использовать __bases__
вместо супер в этом случае и перебирать базовые классы Child
, не зная классов по имени:
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
class Child(Parent1, Parent2):
def on_start(self):
for base in Child.__bases__:
base.on_start(self)
>>> Child().on_start()
do something
do something else
Если есть возможность, у одного из базовых классов нет on_start
, вы можете использовать try/except:
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
class Parent3(object):
pass
class Child(Parent1, Parent2, Parent3):
def on_start(self):
for base in Child.__bases__:
try:
base.on_start(self)
except AttributeError:
# handle that one of those does not have that method
print('"{}" does not have an "on_start"'.format(base.__name__))
>>> Child().on_start()
do something
do something else
"Parent3" does not have an "on_start"
Использование __bases__
будет действовать подобно super
, но для каждой иерархии классов, определенной в определении Child
. т.е. он будет проходить каждый класс ниже, чем on_start
выполняется один раз для каждого родителя класса:
class GGP1(object):
def on_start(self):
print('GGP1 do something')
class GP1(GGP1):
def on_start(self):
print('GP1 do something else')
class Parent1(GP1):
pass
class GGP2(object):
def on_start(self):
print('GGP2 do something')
class GP2(GGP2):
pass
class Parent2(GP2):
pass
class Child(Parent1, Parent2):
def on_start(self):
for base in Child.__bases__:
try:
base.on_start(self)
except AttributeError:
# handle that one of those does not have that method
print('"{}" does not have an "on_start"'.format(base.__name__))
>>> Child().on_start()
GP1 do something else
GGP2 do something
# Note that 'GGP1 do something' is NOT printed since on_start was satisfied by
# a descendant class L to R, bottom to top
Теперь представьте себе более сложную структуру наследования:
Если вы хотите использовать каждый метод on_start
, но можете использовать __mro__
и отфильтровывать классы, у которых нет on_start
, как часть их __dict__
для этого класса. В противном случае вы потенциально получите метод on_start
. Другими словами, hassattr(c, 'on_start')
является True
для каждого класса, который Child
является потомком (кроме object
в этом случае), так как Ghengis
имеет атрибут on_start
, и все классы являются классами потомков из Ghengis.
** Предупреждение - только демонстрация **
class Ghengis(object):
def on_start(self):
print('Khan -- father to all')
class GGP1(Ghengis):
def on_start(self):
print('GGP1 do something')
class GP1(GGP1):
pass
class Parent1(GP1):
pass
class GGP2(Ghengis):
pass
class GP2(GGP2):
pass
class Parent2(GP2):
def on_start(self):
print('Parent2 do something')
class Child(Parent1, Parent2):
def on_start(self):
for c in Child.__mro__[1:]:
if 'on_start' in c.__dict__.keys():
c.on_start(self)
>>> Child().on_start()
GGP1 do something
Parent2 do something
Khan -- father to all
Но это также имеет проблему - если Child
дополнительно подклассифицируется, то дочерний элемент Child также будет циклически пересекать одну и ту же цепочку __mro__
.
Как заявил Раймонд Хеттингер:
super() в деле делегирования вызовов метода для некоторого класса в экземпляры дерева предков. Для перезаписываемых вызовов методов для работы, классы должны разрабатываться совместно. Это представляет три легко решить практические вопросы:
1) метод, вызываемый супер(), должен существовать
2) вызывающий и вызываемый должны иметь соответствующую подпись аргумента и
3) для каждого случая метода необходимо использовать super()
Решение состоит в том, чтобы написать совлокальные классы, которые равномерно используют super
через список предков или творческое использование шаблона адаптера для адаптации классов, которые вы не может контролировать. Эти методы более подробно обсуждаются в статье Pythons super(), рассмотренной супер! Раймондом Хеттингером.