Python self и super в множественном наследовании
в поговорите с Raymond Hettinger это "супер считается супер говорить " на PyCon 2015 он объясняет преимущества использования super
в Python в контексте множественного наследования. Это один из примеров, который Раймонд использовал во время своего выступления:--16-->
class DoughFactory(object):
def get_dough(self):
return 'insecticide treated wheat dough'
class Pizza(DoughFactory):
def order_pizza(self, *toppings):
print('Getting dough')
dough = super().get_dough()
print('Making pie with %s' % dough)
for topping in toppings:
print('Adding: %s' % topping)
class OrganicDoughFactory(DoughFactory):
def get_dough(self):
return 'pure untreated wheat dough'
class OrganicPizza(Pizza, OrganicDoughFactory):
pass
if __name__ == '__main__':
OrganicPizza().order_pizza('Sausage', 'Mushroom')
кто-то в зале попросил Raymond о разнице в использовании self.get_dough()
вместо super().get_dough()
. Я не очень хорошо понял краткий ответ Раймонда, но я закодировал две реализации этот пример, чтобы увидеть различия. Выходные данные одинаковы для обоих случаев:
Getting dough
Making pie with pure untreated wheat dough
Adding: Sausage
Adding: Mushroom
если вы измените порядок классов из OrganicPizza(Pizza, OrganicDoughFactory)
to OrganicPizza(OrganicDoughFactory, Pizza)
используя self.get_dough()
, вы получите такой результат:
Making pie with pure untreated wheat dough
super().get_dough()
это выход:
Making pie with insecticide treated wheat dough
Я понимаю super()
поведение, как объяснил Раймонд. Но каково ожидаемое поведение self
в сценарии множественного наследования?
2 ответов
просто чтобы уточнить, есть четыре случая, основанные на изменении второй строки в Pizza.order_pizza
и определение OrganicPizza
:
-
super()
,(Pizza, OrganicDoughFactory)
(оригинал):'Making pie with pure untreated wheat dough'
-
self
,(Pizza, OrganicDoughFactory)
:'Making pie with pure untreated wheat dough'
-
super()
,(OrganicDoughFactory, Pizza)
:'Making pie with insecticide treated wheat dough'
-
self
,(OrganicDoughFactory, Pizza)
:'Making pie with pure untreated wheat dough'
корпус 3. это тот, который удивил вас; если мы переключим порядок наследования, но все еще используем super
, мы, по-видимому, в конечном итоге называем оригинал DoughFactory.get_dough
.
что super
действительно спрашивает " что дальше в MRO (порядок разрешения метода)?" так что OrganicPizza.mro()
выглядеть?
-
(Pizza, OrganicDoughFactory)
:[<class '__main__.OrganicPizza'>, <class '__main__.Pizza'>, <class '__main__.OrganicDoughFactory'>, <class '__main__.DoughFactory'>, <class 'object'>]
-
(OrganicDoughFactory, Pizza)
:[<class '__main__.OrganicPizza'>, <class '__main__.OrganicDoughFactory'>, <class '__main__.Pizza'>, <class '__main__.DoughFactory'>, <class 'object'>]
решающий вопрос здесь: который приходит после Pizza
? Как мы называем super
внутри Pizza
, вот куда пойдет Python найти get_dough
*. Для 1. и 2. это OrganicDoughFactory
, таким образом, мы получаем чистое, необработанное тесто, но для 3. и 4. это оригинальный, обработанный инсектицидами DoughFactory
.
почему self
разные-то? self
всегда экземпляр, поэтому Python идет искать get_dough
С самого начала MRO. В обоих случаях, как показано выше, OrganicDoughFactory
находится раньше в списке, чем DoughFactory
, поэтому self
версии всегда получить необработанное тесто; self.get_dough
всегда решает OrganicDoughFactory.get_dough(self)
.
* я думаю, что это на самом деле яснее в форме двух аргументов super
используется в Python 2.x, что будет super(Pizza, self).get_dough()
; первый аргумент - класс для пропуска (т. е. Python смотрит в остальную часть MRO после этого класса).
я хотел бы поделиться несколькими наблюдениями по этому поводу.
вызов self.get_dough()
может быть невозможно, если вы не переопределяя get_dough()
метод родительского класса, как здесь:
class AbdullahStore(DoughFactory):
def get_dough(self):
return 'Abdullah`s special ' + super().get_dough()
я думаю, что это частый сценарий на практике. Если мы позвоним DoughFactory.get_dough(self)
сразу после этого поведение исправлено. Класс, выводящий AbdullahStore
придется переопределить
полный метод и не может повторно использовать "добавленную стоимость"AbdullahStore
. С другой стороны, если мы используем тег super.get_dough(self)
, это вкус шаблон:
в любом классе, производном от AbdullahStore
, сказал
class Kebab(AbdullahStore):
def order_kebab(self, sauce):
dough = self.get_dough()
print('Making kebab with %s and %s sauce' % (dough, sauce))
мы не можем инстанцировать' get_dough()
в AbdullahStore
по-другому, перехватывая его в MRO, как это
class OrganicKebab(Kebab, OrganicDoughFactory):pass
вот что он делает:
Kebab().order_kebab('spicy')
Making kebab with Abdullah`s special insecticide treated wheat dough and spicy sauce
OrganicKebab().order_kebab('spicy')
Making kebab with Abdullah`s special pure untreated wheat dough and spicy sauce
С OrganicDoughFactory
имеет одного родителя DoughFactory
, я гарантированно вставляется в MRO прямо перед DoughFactory
и, таким образом, переопределяет его методы для всех предыдущих классов в MRO. Мне потребовалось некоторое время, чтобы понять С3 алгоритм линеаризации, используемый для построения MRO.
Проблема в том, что два правила
children come before parents
parents order is preserved
из этой ссылки https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ пока не определяйте порядок однозначно. В иерархии классов
D->C->B->A
\ /
--E--
(класс A; Класс B (A); Класс C(B); класс E(A); класс D(C,E)) где E будет вставлен в MRO? Это DCBEA или DCEBA? Возможно, прежде чем можно будет уверенно ответить на такие вопросы, это не такая уж хорошая идея, чтобы начать вставлять super
везде. Я все еще не совсем уверен, но я думаю, что линеаризация C3, которая is однозначный и выберет заказ DCBEA в этом примере, делает
позвольте нам сделать трюк перехвата так, как мы это сделали, однозначно.
теперь, я полагаю, вы можете предсказать результат
class KebabNPizza(Kebab, OrganicPizza): pass
KebabNPizza().order_kebab('hot')
который является улучшенным шашлыком:
Making kebab with Abdullah`s special pure untreated wheat dough and hot sauce
но это, вероятно, заняло у вас некоторое время, чтобы вычислять.
когда я впервые увидел super
docs https://docs.python.org/3.5/library/functions.html?highlight=super#super слабый назад, исходя из фона C++, это было похоже на "вау, хорошо, вот правила, но как это может работать и не заглушить вас в спину?".
Теперь я понимаю больше об этом, но все еще неохотно вставляю super
везде. Я думаю, что большая часть кодовой базы, которую я видел, делает это только потому, что super()
удобнее типа чем имя базового класса.
И это даже не говорит о крайнем использовании super()
в привязка __init__
функции. На практике я наблюдаю, что все пишут конструкторы с удобной для класса (а не универсальной) подписью и используют super()
чтобы вызвать то, что они думают, является их конструктором базового класса.