题 用__init __()方法理解Python super()[重复]


这个问题在这里已有答案:

我正在努力了解它的用法 super()。从它的外观来看,可以创建两个子类,就好了。

我很想知道以下两个子课程之间的实际差异。

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1982
2018-02-23 00:30


起源




答案:


super() 让你避免显式引用基类,这可能很好。但主要的优点是多重继承,各种各样的 好玩的东西 可以发生。见 超级标准文档 如果你还没有。

注意 Python 3.0中的语法已更改:你可以说 super().__init__() 代替 super(ChildB, self).__init__() 哪个IMO好一点。


1427
2018-02-23 00:37



你能提供一个例子吗? super() 与参数一起使用? - Steven Vascellaro
你能解释一下吗? super(ChildB, self).__init__() 这个,做什么 ChildB 和 self 与超级有关 - rimiro


我想了解 super()

我们使用的原因 super 这样可以使用协作多重继承的子类将在方法解析顺序(MRO)中调用正确的下一个父类函数。

在Python 3中,我们可以像这样调用它:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

在Python 2中,我们需要像这样使用它:

        super(ChildB, self).__init__()

没有超级,您使用多重继承的能力有限:

        Base.__init__(self) # Avoid this.

我在下面进一步解释。

“这段代码实际上有什么区别?:”

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

这段代码的主要区别在于你获得了一层间接 __init__ 同 super,它使用当前类来确定下一个类 __init__ 在MRO中查找。

我在答案中说明了这种差异 规范问题,如何在Python中使用'super'?,这表明 依赖注入 和 合作多重继承

如果Python没有 super

这里的代码实际上非常相似 super (它是如何在C中实现的,减去一些检查和回退行为,并转换为Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

写得更像本机Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

如果我们没有 super 对象,我们必须在任何地方编写这个手动代码(或重新创建它!),以确保我们在方法解析顺序中调用正确的下一个方法!

超级如何在没有明确告知调用它的方法中的哪个类和实例的情况下在Python 3中执行此操作?

它获取调用堆栈帧,并找到该类(隐式存储为本地自由变量, __class__,使调用函数成为类的闭包)和该函数的第一个参数,该参数应该是通知它使用哪个方法解析顺序(MRO)的实例或类。

因为它需要MRO的第一个参数, 运用 super 用静态方法是不可能的

对其他答案的批评:

使用super()可以避免显式引用基类,这可能很好。 。但主要优势在于多重继承,可以发生各种有趣的事情。如果您还没有,请参阅super上的标准文档。

这是相当手动的,并没有告诉我们太多,但重点 super 不是为了避免编写父类。关键是要确保调用方法解析顺序(MRO)中的下一个方法。这在多重继承中变得很重要。

我会在这里解释一下。

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

让我们创建一个我们希望在Child之后调用的依赖项:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

现在记住, ChildB 使用超级, ChildA 才不是:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

UserA 不调用UserDependency方法:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

UserB因为 ChildB 使用 super,确实!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

对另一个答案的批评

在任何情况下都不应该执行以下操作,这是另一个答案所暗示的,因为当您继承ChildB时肯定会出错:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(这个答案并不聪明或特别有趣,但尽管评论中有直接的批评和超过17个赞成票,但回答者坚持提出建议,直到一位善良的编辑解决了他的问题。)

说明:那个回答建议像这样调用super:

super(self.__class__, self).__init__()

这是 全然 错误。 super 让我们在子类中查找MRO中的下一个父级(请参阅本答案的第一部分)。如果你告诉 super 我们在子实例的方法中,它将在行中查找下一个方法(可能是这个)导致递归,可能导致逻辑失败(在回答者的例子中,它确实)或者 RuntimeError 当超过递归深度时。

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

413
2017-11-25 19:00



我仍然需要解决这个问题 super() 但是,就深度和细节而言,这个答案显然是最好的。我也非常感谢答案中的批评。通过识别其他答案中的缺陷,它还有助于更好地理解这一概念。谢谢 ! - Yohan Obadia
@Aaron Hall,感谢您提供如此详细的信息。我认为如果他们没有提供正确的足够信息,那么导师应该再提供一个选项(至少)来将某些答案称为不恰当或不完整。 - hunch
谢谢,这非常有帮助。对穷人/不正当使用的批评非常说明了为什么以及如何使用超级 - Xarses
Python继承的非常好的解释。谢谢 - ioaniatr


已经注意到在Python 3.0+中你可以使用

super().__init__() 

进行调用,这是简洁的,并且不需要您明确地引用父OR类名称,这可能很方便。我只想在Python 2.7或更低版​​本中添加它,通过编写可以获得这种对名称不敏感的行为 self.__class__ 而不是类名,即

super(self.__class__, self).__init__()

然而,这打破了电话 super 对于从您的类继承的任何类,其中 self.__class__ 可以归还一个儿童班。例如:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

我在这里上课 Square,这是一个子类 Rectangle。假设我不想为其编写单独的构造函数 Square 因为构造函数 Rectangle 很好,但无论出于什么原因我想实现Square,所以我可以重新实现其他方法。

当我创建一个 Square 运用 mSquare = Square('a', 10,10),Python调用构造函数 Rectangle 因为我没有给 Square 它自己的构造函数。但是,在构造函数中 Rectangle,电话 super(self.__class__,self) 将要返回的超类 mSquare,所以它调用构造函数 Rectangle 再次。这就是无限循环的发生方式,正如@S_C所提到的那样。在这种情况下,当我跑 super(...).__init__() 我正在调用构造函数 Rectangle 但既然我没有提出任何论据,我会得到一个错误。


223
2017-10-08 20:08



这个答案表明了什么, super(self.__class__, self).__init__() 如果再次子类而不提供新的,则不起作用 __init__。然后你有一个无限的递归。 - glglgl
这个答案很荒谬。如果您要以这种方式滥用超级,那么您也可以只对基类名称进行硬编码。它没错。超级的第一个论点的全部意义在于它 不 必然是自我的类型。请阅读rhettinger的“超级考虑超级”(或观看他的一些视频)。 - Veky
这里为Python 2演示的快捷方式已经提到了陷阱。不要使用它,否则您的代码将以您无法预测的方式中断。这个“方便的快捷方式”打破了超级,但你可能没有意识到它,直到你沉迷了大量的时间进行调试。如果super太冗长,请使用Python 3。 - Ryan Hiebert
编辑了答案。很抱歉,如果该编辑将含义改为180度,但现在这个答案应该有所帮助。 - Tino
没有意义的是告诉别人他们可以做一些简单地证明是错误的事情。你可以别名 echo 至 python。没有人会建议它! - Aaron Hall♦


超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。


75
2017-11-27 23:26



该 super 调用本身没有副作用,它只是创建一个代理对象。我不确定这个答案试图说的是什么 super 与直接父类相比。 - davidism
声明“超级没有副作用”,在这种背景下没有意义。超级简单地保证我们在方法解析顺序中调用正确的下一个类的方法,而另一种方式硬编码下一个要调用的方法,这使得协作多重继承更加困难。 - Aaron Hall♦


只是一个单挑......用Python 2.7,我相信从那以后 super() 在2.2版中推出,你只能打电话 super()如果其中一个父母继承自最终继承的类 object (新式课程)。

就个人而言,对于python 2.7代码,我将继续使用 BaseClassName.__init__(self, args) 直到我真正获得使用的优势 super()


68
2018-05-25 17:52



非常好的一点。如果你没有明确提到:class Base(object):那么你会得到类似的错误:“TypeError:必须是type,而不是classobj” - andilabs
@andi我前几天得到了这个错误,我最终还是放弃了试图搞清楚。我只是搞乱了iPython。如果这实际上是我必须调试的代码,这是一个糟糕的错误消息的噩梦! - Two-Bit Alchemist


真的没有。 super() 查看MRO中的下一个类(方法解析顺序,使用 cls.__mro__)调用方法。只是打电话给基地 __init__ 打电话给基地 __init__。碰巧的是,MRO只有一个项目 - 基础。所以你真的在做同样的事情,但是用更好的方式 super() (特别是如果你以后进入多重继承)。


46
2018-02-23 00:34



我懂了。你能详细说明为什么使用具有多重继承的super()更好吗?对我来说,基础.__ init __(self)更短(更干净)。如果我有两个基类,那将是两行,或两行()行。或者我是否误解了“更好”的含义? - Mizipzor
实际上,它将是一条super()行。当你有多个继承时,MRO仍然是平的。所以第一个super().__ init__调用调用下一个类 在里面,然后调用下一个,依此类推。你应该查看一些关于它的文档。 - Devin Jeanpierre
子类MRO也包含对象 - 类的MRO在 MRO 类变量。 - James Brady
另请注意,经典类(2.2之前版本)不支持super - 您必须明确引用基类。 - James Brady
“子类MRO也包含对象 - 类中的MRO可见 MRO 类变量。“这是一个很大的哎呀。哎呀。 - Devin Jeanpierre


主要区别在于 ChildA.__init__ 将无条件地打电话 Base.__init__ 而 ChildB.__init__ 将会通知 __init__ 在 无论是什么课程 ChildB 祖先 self祖先的系列 (可能与您的预期不同)。

如果你添加一个 ClassC 使用多重继承:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

然后 Base 不再是父母的 ChildB 对于 ChildC 实例。现在 super(ChildB, self) 会指出 Mixin 如果 self 是一个 ChildC 实例。

你插入了 Mixin 在之间 ChildB 和 Base。你可以利用它 super()

因此,如果您设计了类,以便可以在协作多重继承方案中使用它们,则可以使用 super 因为你真的不知道谁会在运行时成为祖先。

超级考虑的超级职位 和 pycon 2015伴随视频 解释得很好。


22
2017-09-21 06:41



这个。的意思 super(ChildB, self) 根据所引用的对象的MRO而改变 self,直到运行时才能知道。换句话说,作者 ChildB 无法知道什么 super() 除非他们能保证,否则将在所有情况下解决 ChildB 永远不会被分类。 - nispio