18

在PyCon 2015 上Raymond Hettinger 的演讲“超级考虑超级演讲super”中,他解释了在多继承上下文中使用 Python 的优势。这是雷蒙德在演讲中使用的例子之一:

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')

台下有人雷蒙德,self.get_dough()改用的区别super().get_dough()。我不太了解 Raymond 的简短回答,但我编写了此示例的两个实现以查看差异。两种情况的输出相同:

Getting dough
Making pie with pure untreated wheat dough
Adding: Sausage
Adding: Mushroom

如果您将课程顺序从 using 更改OrganicPizza(Pizza, OrganicDoughFactory)OrganicPizza(OrganicDoughFactory, Pizza)using self.get_dough(),您将得到以下结果:

Making pie with pure untreated wheat dough

但是,如果您使用super().get_dough()这是输出:

Making pie with insecticide treated wheat dough

我理解super()雷蒙德解释的行为。但是self在多重继承场景中的预期行为是什么?

4

2 回答 2

14

只是为了澄清,有四种情况,基于更改第二行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始终是 instanceget_dough ,因此 Python从 MRO 开始寻找。在这两种情况下,如上所示,OrganicDoughFactory在列表中都早于DoughFactory,这就是为什么self版本总是得到未经处理的面团;self.get_dough总是解析为OrganicDoughFactory.get_dough(self).


*我认为这super在 Python 2.x 中使用的双参数形式实际上更清楚,即super(Pizza, self).get_dough(); 第一个参数是要跳过的类(即 Python 在该类之后的 MRO 的其余部分中查找)。

于 2015-05-04T23:43:53.450 回答
2

我想分享一些对此的看法。

如果您要覆盖父类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))

我们可以通过像这样在 MRO 中拦截它来“实例化”get_dough()以不同的方式使用AbdullahStore

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有一个 parent DoughFactory,因此可以保证在之前将其插入 MRO 中DoughFactory,从而覆盖其对 MRO 中所有前面类的方法。我花了一些时间来理解用于构建 MRO 的 C3 线性化算法。问题是这两条规则

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 线性化明确的,并且将在此示例中选择排序 DCBEA,确实允许我们以明确的方式执行拦截技巧。

现在,我想你可以预测结果

class KebabNPizza(Kebab, OrganicPizza): pass
KebabNPizza().order_kebab('hot')

这是一个改进的烤肉串:

Making kebab with Abdullah`s special pure untreated wheat dough and hot sauce

但它可能需要你一些时间来计算。

当我第一次查看super文档https://docs.python.org/3.5/library/functions.html?highlight=super#super时,来自 C++ 背景,就像“哇,好的,这是规则,但是这怎么能行得通,而不是在后面给你存根呢?”。现在我对它有了更多的了解,但仍然不愿意super到处插入。我认为我见过的大多数代码库都是这样做的,因为它super()比基类名称更方便输入。这甚至不是说super()在链接__init__函数中的极端使用。我在实践中观察到的是,每个人都使用便于类(而不是通用的)签名的构造函数super()来调用他们认为是他们的基类构造函数。

于 2017-12-02T11:01:17.507 回答