类型为“object”时“super()”的行为?

2024-04-24 00:05:17 发布

您现在位置:Python中文网/ 问答频道 /正文

Python website上找到的关于super()的文档说,它返回一个代理对象,将方法调用委托给父类或同级类。在super considered superhow does super() work with multiple inheritance super considered harmful中找到的信息解释了实际上使用了mro中的下一个方法。我的问题是,如果使用super(object, self).some_method()会发生什么?由于object通常出现在mro列表的末尾,因此我猜搜索会立即到达末尾并出现异常。但事实上,似乎代理本身的方法被调用了,如super(object, self).__repr__()所示,它显示了超级对象本身。我想知道super()object的行为是否根本不是委托方法。 如果是这样的话,我想知道是否有可靠的材料提到过它,以及它是否适用于其他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()

Tags: 对象方法self代理objectwiththisat
2条回答

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为您返回它自己的__init____repr__方法。你知道吗

这是一种“问一个愚蠢的问题(超级),得到一个愚蠢的答案”的情况。也就是说,super应该只使用第一个参数作为定义函数的类来调用。当你用object调用它时,你得到的是未定义的行为。你知道吗

如果super在查看要委托给的方法解析顺序(MRO)时没有找到某个对象(或者如果您正在查找属性__class__),它将检查自己的属性。你知道吗

因为object始终是MRO中的最后一种类型(至少据我所知,它始终是最后一种类型),所以您有效地禁用了委托,它将只检查超级实例。你知道吗


我发现这个问题非常有趣,所以我去了super的源代码,特别是委托部分(^{} (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.

我上面所说的是基于我对cpython3.6源代码的观察,但是我认为对于其他Python版本来说应该没有太大区别,因为其他Python实现(通常)都遵循CPython。你知道吗

事实上,我还检查了:

它们都返回super__repr__。你知道吗

请注意,Python遵循“我们都是同意的成年人”的风格,因此如果有人费心将这种不寻常的用法形式化,我会感到惊讶。我的意思是,谁会试图委托给object(“最终”父类)的兄弟类或父类的方法。你知道吗

相关问题 更多 >