【问题标题】:What exactly does super() return in Python 3? [duplicate]在 Python 3 中 super() 究竟返回了什么? [复制]
【发布时间】:2017-11-27 06:32:51
【问题描述】:

来自 Python3 的文档 super()“返回一个代理对象,它将方法调用委托给类型的父类或同级类。”这是什么意思?

假设我有以下代码:

class SuperClass():
    def __init__(self):
        print("__init__ from SuperClass.")
        print("self object id from SuperClass: " + str(id(self)))

class SubClass(SuperClass):
    def __init__(self):
        print("__init__ from SubClass.")
        print("self object id from SubClass: " + str(id(self)))
        super().__init__()


sc = SubClass()

我从中得到的输出是:

来自子类的 __init__。 来自子类的自我对象 ID:140690611849200 __init__ 来自超类。 来自超类的自我对象 ID:140690611849200

这意味着在super().__init__() 行中,super() 返回当前对象,然后隐式传递给超类的__init__() 方法。这是准确的还是我在这里遗漏了什么?

简单地说,我想了解以下内容:

super().__init__() 运行时,

  1. 究竟是什么传递给__init__() 以及如何传递?我们在 super() 上调用它,所以根据我目前对 Python 的了解,无论返回什么都应该传递给 __init__() 方法。
  2. 为什么我们不必将self 传递给super().__init__()

【问题讨论】:

  • 是什么让你认为它是同一个对象?
  • 因为它们具有与我的示例相同的对象 ID。无论 super 返回什么,不是都传递给__init__吗?
  • 不。 super().__init__() 不会将 super 实例传递给 __init__;它通过了SubClass 实例。
  • 您没有获得 super object 的 ID。您正在打印 Subclass 实例的 ID,因为您打印了 id(self)
  • 您正在使用一个过于简单的心理模型来说明 Python 方法和属性的工作原理。 super().__init__() 的意思是“查找 __init__ 的任何 super() 返回的属性,以及属性查找返回的任何对象,可以是任何东西,调用它”。 super 实现与大多数类型不同的属性查找。

标签: python python-3.x inheritance


【解决方案1】:

返回一个代理对象,它将方法调用委托给父级或 类型的兄弟类。

这个proxy 是一个对象,它充当父类的方法调用部分。它不是类本身;相反,它只是足够的信息,以便您可以使用它来调用父类方法。

如果您调用__init__(),您将获得自己的本地子类__init__ 函数。当您调用super() 时,您将获得该代理对象,该对象会将您重定向到父类方法。因此,当您调用super().__init__() 时,该代理会将调用重定向到父类__init__ 方法。

同样,如果您要调用 super().foo,您会从父类中获得 foo 方法——同样,由该代理重新路由。

你清楚吗?


对 OP 评论的回应

但这一定意味着这个代理对象被传递给 init() 在运行 super() 时。init() 对吗?

错了。代理对象就像一个包名,比如调用math.sqrt()。您没有将数学传递给 sqrt,而是使用它来表示您正在使用哪个 sqrt。如果您想将代理传递给 init,则调用将是 init(super())。当然,这个调用在语义上是荒谬的。

当我们必须实际传入 self 时,在我的示例中是 sc 对象。

不,你不是传入sc;这是对象创建调用(内部方法__new__)的结果,其中包括对init 的调用。对于__init__self 对象是 Python 运行时系统为您创建的新项目。对于大多数类方法,第一个参数(不按惯例称为 self,在其他语言中称为 this)是调用该方法的对象。

【讨论】:

  • 但这一定意味着这个代理对象在运行super().__init__()时被传递给super().__init__()对吧?
  • 当我们必须实际传入 self 时,在我的示例中是 sc 对象。
  • 所以为了澄清,在运行super().__init__()时,self在这里隐式传递给__init__()
  • 好;这对于一个有用的理解来说已经足够接近了。系统提供一个空对象并根据您在__init__ 方法中的命令对其进行更新。你不能传入对象,因为它还不存在——这就是__init__ 正在为你做的事情。
  • @NemindaPrabhashwara:不完全是。 sc 是一个主程序变量,在代理完全实例化和初始化之后,它将在从 __init__ 返回时引用对象。代理确实拥有比其父类更多的方法访问权限......但是 SO cmets 不是讨论这些细节的地方。
【解决方案2】:

这意味着在super().__init__() 行中,super() 返回当前对象,然后隐式传递给超类的__init__() 方法。这是准确的还是我在这里遗漏了什么?

>>> help(super)  
super() -> same as super(__class__, <first argument>)

super 调用返回一个代理/包装对象,它记住:

  • 调用super()的实例

  • 调用对象的类

  • 调用super()的类

这是完美的声音。 super 总是在具有您正在寻找的属性的层次结构(实际上是 MRO)中获取 next 类的属性。所以它不是返回当前对象,而是更准确地说,它返回一个对象,该对象记住了足够的信息来搜索类层次结构中更高的属性。


  1. 究竟是什么传递给__init__() 以及如何传递?我们在 super() 上调用它,所以根据我目前对 Python 的了解,无论返回什么都应该传递给 __init__() 方法。

你几乎是对的。但是super 喜欢捉弄我们。 super类定义了__getattribute__,该方法负责属性搜索。当您执行以下操作时:super().y()super.__getattribute__ 被称为搜索 y。一旦找到y,它将调用super 调用的实例传递给y。另外,super__get__ 方法,这使它成为一个描述符,我将省略描述符的详细信息,请参阅文档了解更多信息。这也回答了您的第二个问题,即为什么 self 没有明确传递。

*注意:super 有点不同,它依赖于一些魔法。几乎所有其他类的行为都是相同的。那就是:

a = A()  # A is a class 
a.y()    # same as A.y(a), self is a 

super 不同:

class A:
    def y(self):
        return self
class B(A):
    def y(self)
        return super().y()   # equivalent to: A.y(self)
b = B()
b.y() is b  # True: returns b not super(), self is b not super()

【讨论】:

  • 它似乎只记得两件事,而不是三件事
  • 在实例方法中调用super时,proxy对象中会出现三个东西:__thisclass____self_class____self__,这三个都是根据调用的地方设置的super 通话发生。
  • @NeilG 是的,对。 __thisclass__ 选择 MRO 中的下一个班级。您在技术上是正确的,但取决于 super 的内部实现,我不知道外部 Python 代码。
  • @NeilG __self__ 可能是 None 现在我明白你的意思了。
【解决方案3】:

我写了一个简单的测试来调查 CPython 对 super 做了什么:

class A:
    pass

class B(A):
    def f(self):
        return super()

    @classmethod
    def g(cls):
        return super()

    def h(selfish):
        selfish = B()
        return super()

class C(B):
    pass

c = C()
for method in 'fgh':
    super_object = getattr(c, method)()
    print(super_object, super_object.__self__, super_object.__self_class__, super_object.__thisclass__)  # (These methods were found using dir.)

零参数super 调用返回一个存储三个东西的对象:

  • __self__ 存储名称与方法的第一个参数匹配的对象,即使该名称已被重新分配。
  • __self_class__ 存储它的类型,或者在类方法的情况下存储它自己。
  • __thisclass__ 存储了定义方法的类。

(不幸的是,__thisclass__ 是以这种方式实现的,而不是在方法上获取属性,因为它使得不可能在元编程中使用 super 的零参数形式。)

super 返回的对象实现了getattribute,它将方法调用转发到在__self_class____mro__ 中找到的类型在__thisclass__ 之后的一级。

【讨论】:

  • (此答案基于@direprobs 的答案。)
猜你喜欢
  • 2017-02-11
  • 2017-10-30
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-21
  • 1970-01-01
相关资源
最近更新 更多