【问题标题】:How does `super` interacts with a class's `__mro__` attribute in multiple inheritance?`super` 在多重继承中如何与类的 `__mro__` 属性交互?
【发布时间】:2015-11-24 10:16:29
【问题描述】:

今天,我阅读了official doc of super
其中提到多重继承将由类的__mro__ 属性决定。
于是我做了一些实验,结果让我吃惊。

# CODE PART
class GrandFather(object):
    def p(self):
        print "I'm old."

class Father(GrandFather):
    def p(self):
        print "I'm male."

class Mother(object):
    def p(self):
        print "I'm female."

class Son(Father, Mother):
    def p(self):
        print "busy, busy, crwaling. "


 # EXPERIMENT PART
In [1]: Son.__mro__
Out[1]: (__main__.Son, __main__.Father, __main__.GrandFather, __main__.Mother, object)

In [2]: Father.__mro__
Out[2]: (__main__.Father, __main__.GrandFather, object)

In [3]: Mother.__mro__
Out[3]: (__main__.Mother, object)

In [4]: GrandFather.__mro__
Out[4]: (__main__.GrandFather, object)

In [5]: s = Son()

In [6]: super(Son, s).p()
I'm male.

In [7]: super(Father, s).p()
I'm old.

In [8]: super(Mother, s).p()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-8-ce4d0d6ef62d> in <module>()
----> 1 super(Mother, s).p()

AttributeError: 'super' object has no attribute 'p'

In [9]: super(GrandFather, s).p()
I'm female.

下面是我上面提到的官方文档的一部分,上面写着:

super(type[, object-or-type])
Return a proxy object that delegates method calls to a parent or sibling class of type.   
This is useful for accessing inherited methods that have been overridden in a class.  
The search order is same as that used by getattr() except that the type itself is skipped.

The __mro__ attribute of the type lists the method resolution search order  
used by both getattr() and super().   
The attribute is dynamic and can change whenever the inheritance hierarchy is updated.

If the second argument is an object, isinstance(obj, type) must be true.

通过结合这个文档和我的实验结果。最令人困惑的部分是,当使用super(GrandFather, s).p() 调用时,它调用了Motherp(),但Mother 不在GrandFather__mro__ 中,而且它的顺序非常劣于@987654331 @的__mro__

经过一番琢磨。我得到了一个合理的解释,表明官方文档的不完整或不足:
也就是说,当与super(type, instance) 一起使用时,super 函数将从class__mro__ 属性中搜索您的instance 的构建者,而不是您传递的type__mro__ 属性到super,即使它满足isinstance(instance, type) 条件。

所以当你输入super(Class, instance) 时发生的事情是:

  1. Python 检查isinstance(instance, Class) 是否为真。
  2. Python 找到instance__class__ 属性,
    获取instance.__class____mro__ 属性。
  3. Python 在第二步的__mro__ 元组中找到你传递给superClass 的索引。
  4. Python将step3的索引加1,用它来获取step2的__mro__元组中对应的类,并返回这个对应类的super delegate。
  5. 如果step4的索引超过step2的__mro__的长度,则返回step2的__mro__最后一个类的delegate,即object类。

我的理解对吗?
如果我错了,supertype__mro__ 交互的正确机制是什么?
如果我是对的,我应该如何提出 python 官方文档修改的问题?
因为我认为关于这个项目的当前版本可能会产生误导。


PS:本次测试由Python 2.7.6 within IPython 3.2.1完成。

【问题讨论】:

  • 好问题,令人惊讶的行为!

标签: python inheritance superclass super


【解决方案1】:

Son__mro__

__main__.Son, __main__.Father, __main__.GrandFather, __main__.Mother, object

根据文档:

类型的__mro__属性列出方法解析搜索顺序

所以方法将根据__mro__列表中的顺序从左到右进行搜索。调用super(type, instance) 会将起始位置更改为指定为super() 的第一个参数的类型,在__mro__ 列表中指定为第二个参数的实例的类(如果传递给super 的第二个参数是一个实例):

super(Son, s) 将代理到__main__.Father

super(Father, s) 将代理到__main__.GrandFather

super(GrandFather, s) 将代理到__main__.Mother

super(Mother, s) 将代理到object

有趣的部分是为什么Son__mro__ 是这样的。换句话说,为什么妈妈在追爷爷。这是因为线性化在 python 中的工作方式:

C 的线性化是 C 的总和加上双亲的线性化和双亲列表的合并。

请参阅您提到的documentation 中的示例,它解释了一个非常相似的案例。

所以最终结果实际上是正确的:super(GrandFather, s).p() 应该是 I'm female.

【讨论】:

  • "调用 super(type, instance) 会将起始位置更改为指定为 super() 的第一个参数的类型" - 这确实帮助我理解了结果,谢谢。
  • 是的,我琢磨了一下就明白了。然后我更新了我的问题。但问题是,这里的文档是模棱两可的。看看doc中super的用法示例:super(type[, object-or-type]),这里有两个类型的名字,对吧?完全没有视觉上的区别。然后让我们看看文档中的__mro__ 描述:The __mro__ attribute of the type lists the method resolution search order used by both getattr() and super(). 因为我可以将对象/实例和类型都传递给super。如果没有规范,学习者不应该自然认为第一种类型是 doc 语句所指的吗?
  • 另外,在super 下的唯一示例中,它使用super(type, object) 语句来调用超级。在这里学习者应该如何自然地理解我们使用对象的类型__mro__和当前版本文档的描述?
  • @Mariusz Jamro,提醒您在回答 in the __mro__ list of the instance specified as the second argument 中的声明。该实例没有__mro__ 属性,生成此实例的原始类具有__mro__ 属性。所以更准确的说法应该是in the __mro__ list of the original class where the instance generates from (if the second argument passed to super is a instance)
【解决方案2】:

来自learning python的第32章:

每个超级调用都从方法调用的自身主题的类的 MRO 排序中的下一个类中选择方法。

所以对于super(cls, instance)(isinstance(instance, cls)必须是True),方法从instance.__class__.__mro__中的下一个类中选择,从instance.__class__开始。

对于super(cls0, cls1)(issubclass(cls1, cls0)必须是True),方法从cls1.__mro__的下一个类中选择,从cls0开始

在这两种情况下,如果 MRO 链中的下一个类没有实现该方法,则搜索将向前跳过,直到找到一个定义了该方法的类。

【讨论】:

    猜你喜欢
    • 2022-12-18
    • 2020-03-20
    • 1970-01-01
    • 1970-01-01
    • 2019-05-15
    • 1970-01-01
    • 2023-01-14
    • 2018-01-27
    相关资源
    最近更新 更多