1

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

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())“A级”应该在哪里,对吧?如果第一行的 C 是 DI,那么(在某种程度上)可以理解,但在我看来,第二行绝对应该是 A。

有什么帮助吗?

编辑:我的理解是调用

super(C, obj).name()

将导致名称“A”,因为 C 的线性化是[C, A, object].

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

4

2 回答 2

3

super()不看__bases__;它通过以下方式查看方法解析顺序 (MRO) type(self).mro()

>>> E.mro()
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <type 'object'>]

如您所见,D在其中,因为它是 ; 的基类E。当您打电话时super(C, E()).name()DMRO 中的下一个。

MRO 将始终包含层次结构中的所有基类;您无法建立无法建立 MRO 的类层次结构。这是为了防止在菱形继承模式中跳过类。

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

您可能还想阅读Guido van Rossum 的解释;他使用菱形图案:

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().

如果您真的想跳过 Dfrom ,则必须在 MROC.name中显式查找,然后告诉从那里开始搜索下一个方法:C.__bases__[0]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()方法位于A此处。

于 2014-03-04T16:02:52.843 回答
0

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

如果有人想要产生我错误地期望从 super 得到的结果,可以使用一种基于@Sven Marnach 在python 上接受的答案的方法:在指定类开始 MRO 搜索的类似 super() 的代理对象

如果你想获得一些作为实例的类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(),它会调用 on A, never D。然而,它确实让你从一个类到根获得一个(不是“那个”)直接祖先线。此外,parent(Cls, obj)将始终是 的父obj级,Cls而不是一无所知但恰好是 的祖先的类obj

于 2014-03-05T19:45:33.740 回答