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:

  1. super(), (Pizza, OrganicDoughFactory) (оригинал): 'Making pie with pure untreated wheat dough'
  2. self, (Pizza, OrganicDoughFactory): 'Making pie with pure untreated wheat dough'
  3. super(), (OrganicDoughFactory, Pizza): 'Making pie with insecticide treated wheat dough'
  4. 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() чтобы вызвать то, что они думают, является их конструктором базового класса.