Python __bases__和super

时间:2014-03-04 15:59:48

标签: python python-2.7 inheritance super diamond-problem

在Python 2.7中,我试图重建从某个类E到根A的AN继承链。有一个钻石继承问题,如下所示,但我对路径感兴趣,而不是路径,所以它应该工作。我是否应该这样做(这种方式)是值得怀疑的,但是现在我只是想知道我误解了什么......

class A(object):
    @classmethod
    def supah(thisCls):
        return [cls for cls in thisCls.__bases__ if issubclass(cls, A)]
    def name(self):
        return 'A'

class C(A):
    def name(self):
        return 'C'

class D(A):
    def name(self):
        return 'D'

class E(C, D):
    def name(self):
        return 'E'

current = E
while True:
    try:
        print current, super(current, E()), super(current, E()).name()
    except AttributeError:
        break
    current = current.supah()[0]

输出

<class '__main__.E'> <super: <class 'E'>, <E object>> C
<class '__main__.C'> <super: <class 'C'>, <E object>> D
<class '__main__.A'> <super: <class 'A'>, <E object>>

D在那做什么?它在呼叫

super(C, E()).name()

super(C, E())应该是&#34; A&#34;,对吧?如果第一行的C是D,我会(有点)理解,但在我看来,第二行绝对应该是A。

任何帮助?

编辑:我的理解是调用

super(C, obj).name()

会产生名称&#34; A&#34;,因为C的线性化为[C, A, object]

然而,这不是super(C, obj).name()显然意味着什么。它仍然使用obj的完全线性化:[E, C, D, A, object](感谢@Martijn Pieters),它只是在(之后)C开始。因此D出现在A之前。

2 个答案:

答案 0 :(得分:3)

super()不看__bases__;它通过type(self).mro()

查看方法解决顺序(MRO)
>>> E.mro()
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <type 'object'>]

如您所见,D就在那里,因为它是E的基类;当您拨打super(C, E()).name()时,MRO中会出现D

MRO将始终在层次结构中包含所有基类;你不能建立一个无法建立MRO的类层次结构。这样可以防止在菱形继承模式中跳过类。

The Python 2.3 Method Resolution Order中详细解释了MRO的工作原理。

您可能还想阅读Guido van Rossum's explanation;他使用钻石图案:

class A:
  def save(self): pass

class B(A): pass

class C(A):
  def save(self): pass

class D(B, C): pass

说明为什么MRO很重要;在调用D().save()时,您需要调用C.save()(更专业), A.save()

如果你真的想从D 跳过 C.name,你必须在MRO中明确找到C.__bases__[0],然后告诉{{1} }从那里开始搜索下一个super()方法:

.name()

对于您的mro = type(self).mro() preceding = mro[0] for i, cls in enumerate(mro[1:], 1): if cls in self.__bases__: preceding = mro[i - 1] name = super(preceding, self).name() 和课程E.mro(),这会找到C,因为它位于D的第一个基类C之前。致电A然后告诉super(D, self).name()使用super()方法查找过去D的第一个班级,此处为name()

答案 1 :(得分:0)

@Martijn Pieters的答案解释了如何产生观察结果。

如果有人希望产生我错误地期望从super获得的结果,可以根据@Sven Marnach在python: super()-like proxy object that starts the MRO search at a specified class上接受的答案使用方法

如果您希望得到的行为与A个实例的C个版本相同:

class Delegate:
    def __init__(self, cls, obj):
        self._delegate_cls = cls
        self._delegate_obj = obj
    def __getattr__(self, name):
        x = getattr(self._delegate_cls, name)
        if hasattr(x, "__get__"):
            return x.__get__(self._delegate_obj)
        return x

可用于从.name()获取A,如下所示:

class C(A):
    def name(self):
        return delegate(A, self).name() + 'C'
C().name()
# result: AC

如果您对获得(第一个)直接祖先的超级构造感兴趣:

class parent:
    def __init__(self, cls, obj):
        if len(cls.__bases__):
            self._delegate_cls = cls.__bases__[0]
        else:
            raise Exception('parent called for class "%s", which has no base classes')
        self._delegate_obj = obj
    def __getattr__(self, name):
        x = getattr(self._delegate_cls, name)
        if hasattr(x, '__get__'):
            return x.__get__(self._delegate_obj)
        return x

这样叫:

class C(A):
    def name(self):
        return parent(C, self).name() + 'C'
print C().name()
# result: AC

我认为没有办法不明确地包含当前类的名称,就像super(在py2中)。

请注意,这适用于特殊情况。例如。在我的示例中,如果C未实现.name(),则会调用A上的那个,而不是D。然而,它确实让你从一个类到根来获得一个(不是''')直接祖先行。此外,parent(Cls, obj)始终是obj的父级,而不是Cls一无所知的类,但恰好是obj的祖先。