当类型为 'object' 时 'super()' 的行为?

The behaviour of 'super()' when type is 'object'?

Python website says it returns a proxy object that delegates method calls to parent or sibling class. Information found at super considered super, how does super() work with multiple inheritance and super considered harmful 上找到的关于 super() 的文档说明实际上使用了 mro 中的下一个方法。我的问题是,如果使用 super(object, self).some_method() 会怎样?由于 object 通常出现在 mro 列表的末尾,我猜搜索将立即结束,但出现异常。但实际上好像是调用了proxy本身的方法,如super(object, self).__repr__()显示super对象本身。我想知道 super()object 的行为是否是 根本不委托方法 。 如果是这种情况,我想知道任何可靠的 material 曾经提到过它以及它是否适用于其他 Python 实现。

class X(object):
    def __init__(self):
        # This shows [X, object].
        print X.mro()

        # This shows a bunch of attributes that a super object can have.
        print dir(super(object, self))

        # This shows something similar to <super object at xxx>
        print(object, self)

        # This failed with `super() takes at least one argument`
        try:
            super(object, self).__init__()
        except:
            pass

        # This shows something like <super <class 'object'>, <'X' object>>.
        print(super(object, self).__repr__())

        # This shows the repr() of object, like <'X' object at xxx>
        print(super(X, self).__repr__())


if __name__ == '__main__':
    X()

super 定义了一些自己的属性,需要一种方法来提供对它们的访问。首先是使用 __dunder__ 风格,Python 为自己保留,并表示任何库或应用程序都不应定义以双下划线开头和结尾的名称。这意味着 super 对象可以确信没有什么会与其 __self____self_class____thisclass__ 的属性发生冲突。因此,如果它搜索 mro 并且没有找到请求的属性,那么它会转而尝试在超级对象本身上找到该属性。例如:

>>> class A:
    pass

>>> class B(A):
    pass

>>> s = super(A, B())
>>> s.__self__
<__main__.B object at 0x03BE4E70>
>>> s.__self_class__
<class '__main__.B'>
>>> s.__thisclass__
<class '__main__.A'>

由于您已将 object 指定为开始查看的类型,并且因为 object始终 mro 中的最后一个类型,所以有没有可能的候选者可以为其获取方法或属性。在这种情况下,super 的行为就好像它已经尝试了各种类型来寻找名称,但没有找到一个。所以它试图从自身获取属性。但是,由于 super 对象也是一个对象,它可以访问 __init____repr__ 以及 object 定义的所有其他对象。因此 super returns 它自己的 __init____repr__ 方法供您使用。

这是"ask a silly question (of super) and get a silly answer"的一种情况。也就是说, super 应该只用第一个参数作为定义函数的 class 来调用。当你用 object 调用它时,你会得到未定义的行为。

如果 super 在查看要委托给的方法解析顺序 (MRO) 时没有找到任何东西(或者如果您正在寻找属性 __class__),它将检查自己属性。

因为 object 始终是 MRO 中的最后一个类型(至少据我所知,它始终是最后一个)您有效地禁用了委派并且它将 检查超级实例。


我发现这个问题非常有趣,所以我查看了 super 的源代码,特别是委托部分 (super.__getattribute__ (in CPython 3.6.5)) 并将其(大致)翻译成纯 Python 附上我自己的一些补充意见:

class MySuper(object):
    def __init__(self, klass, instance):
        self.__thisclass__ = klass
        self.__self__ = instance
        self.__self_class__ = type(instance)

    def __repr__(self):
        # That's not in the original implementation, it's here for fun
        return 'hoho'

    def __getattribute__(self, name):
        su_type = object.__getattribute__(self, '__thisclass__')
        su_obj = object.__getattribute__(self, '__self__')
        su_obj_type = object.__getattribute__(self, '__self_class__')

        starttype = su_obj_type

        # If asked for the __class__ don't go looking for it in the MRO!
        if name == '__class__':
            return object.__getattribute__(self, '__class__')
        mro = starttype.mro()
        n = len(mro)

        # Jump ahead in the MRO to the passed in class 
        # excluding the last one because that is skipped anyway.
        for i in range(0, n - 1):
            if mro[i] is su_type:
                break
        # The C code only increments by one here, but the C for loop
        # actually increases the i variable by one before the condition
        # is checked so to get the equivalent code one needs to increment
        # twice here.
        i += 2
        # We're at the end of the MRO. Check if super has this attribute.
        if i >= n:
            return object.__getattribute__(self, name)

        # Go up the MRO
        while True:
            tmp = mro[i]
            dict_ = tmp.__dict__
            try:
                res = dict_[name]
            except:
                pass
            else:
                # We found a match, now go through the descriptor protocol
                # so that we get a bound method (or whatever is applicable)
                # for this attribute.
                f = type(res).__get__
                f(res, None if su_obj is starttype else su_obj, starttype)
                res = tmp
                return res

            i += 1
            # Not really the nicest construct but it's a do-while loop
            # in the C code and I feel like this is the closest Python
            # representation of that.
            if i < n:
                continue
            else:
                break

        return object.__getattribute__(self, name)

如您所见,您可以通过一些方法最终查找 super 上的属性:

  • 如果您正在寻找 __class__ 属性
  • 如果您立即到达 MRO 的末尾(通过传入 object 作为第一个参数)!
  • 如果 __getattribute__ 在剩余的 MRO 中找不到匹配项。

实际上,因为它的工作方式与 super 相似,您可以改用它(至少就属性委托而言):

class X(object):
    def __init__(self):
        print(MySuper(object, self).__repr__())

X()

这将从 MySuper.__repr__ 打印 hoho。通过插入一些 print 来跟随控制流,随意试验该代码。

I wonder any reliable material ever mentions it and whether it applies to other Python implementations.

我上面所说的是基于我对 CPython 3.6 源代码的观察,但我认为对于其他 Python 版本应该不会有太大差异,因为另一个 [=76] =] 实现(通常)遵循 CPython。

其实我也查过:

所有 return super__repr__

请注意 Python 遵循 "We are all consenting adults" 风格,所以如果有人费心将这种 不寻常的 用法正式化,我会感到惊讶。我的意思是谁会尝试委托给 object 的兄弟或父 class 的方法("ultimate" 父 class)。