Python 多重继承中的 self 和 super
Python self and super in multiple inheritance
在 Raymond Hettinger 在 PyCon 2015 的演讲“Super considered super speak”中,他解释了在多重继承上下文中使用 super
in Python 的优势。这是 Raymond 在演讲中使用的例子之一:
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
如果您使用 self.get_dough()
将 class 顺序从 OrganicPizza(Pizza, OrganicDoughFactory)
更改为 OrganicPizza(OrganicDoughFactory, Pizza)
,您将得到以下结果:
Making pie with pure untreated wheat dough
但是如果你使用 super().get_dough()
这是输出:
Making pie with insecticide treated wheat dough
我理解 Raymond 解释的 super()
行为。但是 self
在多重继承场景中的预期行为是什么?
澄清一下,有四种情况,基于更改 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
真正做的是问"which is next in the MRO (method resolution order)?"那么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
之后是哪个?当我们从 Pizza
内部调用 super
时,这就是 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()
;第一个参数是要跳过的 class(即 Python 在 class 之后查看 MRO 的其余部分)。
我想就此分享一些看法。
如果覆盖父 class 的 get_dough()
方法,则可能无法调用 self.get_dough()
,如下所示:
class AbdullahStore(DoughFactory):
def get_dough(self):
return 'Abdullah`s special ' + super().get_dough()
我认为这是实践中经常出现的场景。如果我们直接调用 DoughFactory.get_dough(self)
那么行为是固定的。 class 派生 AbdullahStore
必须覆盖
完整的方法,不能重复使用 AbdullahStore
的 'added value'。另一方面,如果我们使用 super.get_dough(self)
,这具有模板的风格:
在从 AbdullahStore
派生的任何 class 中,比如说
class Kebab(AbdullahStore):
def order_kebab(self, sauce):
dough = self.get_dough()
print('Making kebab with %s and %s sauce' % (dough, sauce))
我们可以 'instantiate' 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
,我保证它会在 DoughFactory
之前插入 MRO,从而覆盖所有前面的方法 class在 MRO 中。我花了一些时间来理解用于构建 MRO 的 C3 线性化算法。
问题是两条规则
children come before parents
parents order is preserved
根据此参考 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 尚未明确定义顺序。在 class 层次结构中
D->C->B->A
\ /
--E--
(class A; class B(A); class C(B); class E(A); class 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++ 背景,就像 "wow, ok here are the rules, but how this can ever work and not stub you in the back?"。
现在我对它有了更多的了解,但仍然不愿意到处插入 super
。我认为我看到的大多数代码库都是这样做的,只是因为 super()
比基础 class 名称更便于键入。
这甚至不是在谈论 super()
在链接 __init__
函数时的极端使用。我在实践中观察到的是,每个人都使用方便 class(而不是通用的)的签名编写构造函数,并使用 super()
调用他们认为是他们的基础 class构造函数。
在 Raymond Hettinger 在 PyCon 2015 的演讲“Super considered super speak”中,他解释了在多重继承上下文中使用 super
in Python 的优势。这是 Raymond 在演讲中使用的例子之一:
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
如果您使用 self.get_dough()
将 class 顺序从 OrganicPizza(Pizza, OrganicDoughFactory)
更改为 OrganicPizza(OrganicDoughFactory, Pizza)
,您将得到以下结果:
Making pie with pure untreated wheat dough
但是如果你使用 super().get_dough()
这是输出:
Making pie with insecticide treated wheat dough
我理解 Raymond 解释的 super()
行为。但是 self
在多重继承场景中的预期行为是什么?
澄清一下,有四种情况,基于更改 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
真正做的是问"which is next in the MRO (method resolution order)?"那么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
之后是哪个?当我们从 Pizza
内部调用 super
时,这就是 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()
;第一个参数是要跳过的 class(即 Python 在 class 之后查看 MRO 的其余部分)。
我想就此分享一些看法。
如果覆盖父 class 的 get_dough()
方法,则可能无法调用 self.get_dough()
,如下所示:
class AbdullahStore(DoughFactory):
def get_dough(self):
return 'Abdullah`s special ' + super().get_dough()
我认为这是实践中经常出现的场景。如果我们直接调用 DoughFactory.get_dough(self)
那么行为是固定的。 class 派生 AbdullahStore
必须覆盖
完整的方法,不能重复使用 AbdullahStore
的 'added value'。另一方面,如果我们使用 super.get_dough(self)
,这具有模板的风格:
在从 AbdullahStore
派生的任何 class 中,比如说
class Kebab(AbdullahStore):
def order_kebab(self, sauce):
dough = self.get_dough()
print('Making kebab with %s and %s sauce' % (dough, sauce))
我们可以 'instantiate' 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
,我保证它会在 DoughFactory
之前插入 MRO,从而覆盖所有前面的方法 class在 MRO 中。我花了一些时间来理解用于构建 MRO 的 C3 线性化算法。
问题是两条规则
children come before parents
parents order is preserved
根据此参考 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 尚未明确定义顺序。在 class 层次结构中
D->C->B->A
\ /
--E--
(class A; class B(A); class C(B); class E(A); class 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++ 背景,就像 "wow, ok here are the rules, but how this can ever work and not stub you in the back?"。
现在我对它有了更多的了解,但仍然不愿意到处插入 super
。我认为我看到的大多数代码库都是这样做的,只是因为 super()
比基础 class 名称更便于键入。
这甚至不是在谈论 super()
在链接 __init__
函数时的极端使用。我在实践中观察到的是,每个人都使用方便 class(而不是通用的)的签名编写构造函数,并使用 super()
调用他们认为是他们的基础 class构造函数。