多重继承中的Python self和super

时间:2015-05-04 23:23:30

标签: python inheritance self super method-resolution-order

在Raymond Hettinger在PyCon 2015上的演讲“Super considered super speak”中,他解释了在多重继承上下文中在Python中使用super的优点。这是雷蒙德在演讲中使用的一个例子:

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

观众中有人asked Raymond关于使用self.get_dough()代替super().get_dough()的不同之处。我对Raymond的简要回答并不是很了解,但我编写了这个例子的两个实现来看看差异。两种情况的输出相同:

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

如果您使用OrganicPizza(Pizza, OrganicDoughFactory)将班级顺序从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 个答案:

答案 0 :(得分:12)

为了澄清,有四种情况,基于更改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'
  5. 案例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会从MRO的开头寻找get_dough。在这两种情况下,如上所示,OrganicDoughFactory在列表中的位置早于DoughFactory,这就是self版本总是未经处理的原因; self.get_dough始终解析为OrganicDoughFactory.get_dough(self)

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

答案 1 :(得分: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))

我们可以实例化&#39; get_dough() AbdullahStoreclass OrganicKebab(Kebab, OrganicDoughFactory):pass 中的使用方式不同,通过在MRO中拦截它,就像这样

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有一个父DoughFactory,我保证在children come before parents parents order is preserved 之前插入MRO,因此会覆盖MRO中所有前面类的方法。我花了一些时间来理解用于构建MRO的C3线性化算法。 问题是两条规则

D->C->B->A
 \      /
   --E--
来自此引用https://rhettinger.wordpress.com/2011/05/26/super-considered-super/

尚未明确定义排序。在类层次结构中

super

(A类; B类(A); C类(B); E(A)类; D类(C,E))其中E将被插入MRO?是DCBEA还是DCEBA?也许在人们可以自信地回答这样的问题之前,开始在任何地方插入class KebabNPizza(Kebab, OrganicPizza): pass KebabNPizza().order_kebab('hot') 并不是一个好主意。我仍然不完全确定,但我认为C3线性化,其中 明确并将在此示例中选择排序DCBEA, 允许我们按照我们这样做的方式进行拦截技巧,毫不含糊。

现在,我想你可以预测

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

这是一个改进的烤肉串:

super

但它可能花了你一些时间来计算。

当我第一次看到super文档https://docs.python.org/3.5/library/functions.html?highlight=super#super弱势群体,来自C ++背景时,它就像是#34;哇,这里的规则是好的,但这是如何工作的而不是把你困在后面?&#34;。 现在我对它有了更多的了解,但仍然不愿意到处插入super()。我认为我看到的大多数代码库只是因为super()比基类名更方便键入。 这甚至没有谈到__init__在链接super()函数时的极端用法。我在实践中观察到的是,每个人都使用便于类(而不是通用的)的签名编写构造函数,并使用function count_order_no( $atts, $content = null ) { $args = shortcode_atts( array( 'status' => 'cancelled', ), $atts ); $statuses = array_map( 'trim', explode( ',', $args['status'] ) ); $order_count = 0; foreach ( $statuses as $status ) { // if we didn't get a wc- prefix, add one if ( 0 !== strpos( $status, 'wc-' ) ) { $status = 'wc-' . $status; } $order_count += wp_count_posts( 'shop_order' )->$status; } ob_start(); echo number_format( $order_count ); return ob_get_clean(); } add_shortcode( 'wc_order_count', 'count_order_no' ); 来调用他们认为是他们的基类构造函数。