了解Python中的super()和__init__()方法

3200

为什么要使用super()

使用Base.__init__super().__init__有什么区别吗?

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

29
这是一个非常简单的类入门,值得一读:https://realpython.com/python-super/#an-overview-of-pythons-super-function。相较于其他对 Python 实现细节过于详尽的解答,这篇文章更易理解。它也提供了具体的示例。 - Charlie Parker
我还是不明白。我想定义一个类Event(tuple),它创建元组(时间戳,描述),其中时间戳应默认为当前时间。因此,类似于e = Event(description="stored the current time")这样的东西应该给出一个元组的子类Event的实例(1653520485,"stored...")。但在__init __()中,我不能像对字典的子类那样修改self。所以我想我可以使用super().__init__来设置元组self的组件。我可以吗? - Max
7个回答

2314

super() 可以让你避免显式地引用基类,这很方便。但主要优点在于多重继承,各种有趣的事情就可能发生。如果你还没有看过,可以参考super的标准文档

请注意,在Python 3.0中语法已经改变:你只需要说super().__init__()而不是super(ChildB, self).__init__(),我认为这样更好。标准文档还提到了一个使用super()的指南,非常详尽。


13
super() 的语法是 super([type [, object]]),它将返回 type 的超类。因此,在这种情况下将返回 ChildB 的超类。如果省略第二个参数,则返回未绑定的 super 对象。如果第二个参数是一个对象,则 isinstance(object, type) 必须为真。 - Omnik
11
如果您在这里仍感到困惑,请阅读Aaron Hall的答案,您将离开此页面时更加愉快:https://dev59.com/zXRB5IYBdhLWcg3wl4Oc#27134600 - eric
12
你能否实际解释一下这段代码的作用?我不想再点击一百万个地方才能找到答案。 - Charlie Parker
2
Python程序员对“乐趣”的奇怪定义在于多重继承带来的主要优势,可以发生各种有趣的事情。 - Marcos Brigante

1233

我正在尝试理解super()

我们使用super的原因是为了让可能使用协作式多继承的子类在方法解析顺序(MRO)中调用正确的下一个父类函数。

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

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

在Python 2中,我们需要使用定义类的名称和self这样调用super,但从现在开始我们将避免这种方式,因为它是多余的、较慢(由于名称查找)和更冗长(所以如果你还没有更新你的Python,请尽快更新!):
        super(ChildB, self).__init__()

如果没有超级(super),您在使用多重继承方面的能力会受到限制,因为您将下一个父类的调用硬编码:

        Base.__init__(self) # Avoid this.

我下面进一步解释。

“这段代码有什么不同之处呢?”

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

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

在这段代码中,与 ChildA 相比,ChildB 中的 __init__ 使用了 super 进行了一层间接引用,使用定义它的类来确定下一个类在 MRO 中要查找的 __init__
我在经典问题 "如何在 Python 中使用 'super'" 的答案中举例说明了这个区别,演示了依赖注入协同多重继承

如果 Python 没有 super

以下是实际上与super(C 语言中的实现方式,减去一些检查和回退行为,并转换为 Python)密切相关的代码:
class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        check_next = mro.index(ChildB) + 1 # next 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中的super是如何做到这一点的呢?它获取调用堆栈帧,并查找类(隐式存储为局部自由变量__class__,使得调用函数成为该类的闭包)和该函数的第一个参数,该参数应该是实例或类,通知它使用哪个方法解析顺序(MRO)。由于它需要该MRO的第一个参数,因此在静态方法中使用super是不可能的,因为它们无法访问从中调用它们的类的MRO
其他答案的批评: 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().__init__()

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

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

请记住,ChildB 使用 super 关键字,ChildA 则没有使用:

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

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

而且UserA没有调用UserDependency方法:

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

但是实际上,UserB 调用了 UserDependency,因为 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()中使用self.__class__替代类名会导致递归。 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'

Python 3的新super()调用方法不需要参数,幸运的是,这使我们能够回避这个问题。


64
我仍然需要理解super()函数,但这个答案在深度和细节方面显然是最好的。我也非常欣赏答案中的批评。通过指出其他答案的缺陷,它也有助于更好地理解概念。谢谢! - Yohan Obadia
5
我之前一直使用tk.Tk.__init__(self)而非super().__init__(),因为我不完全理解super的含义,但是这篇文章让我茅塞顿开。我猜在Tkinter类中,tk.Tk.__init__(self)super().__init__()是一样的,但你似乎在说我们应该避免像Base.__init__(self)这样的写法,所以我可能会转而使用super(),尽管我仍在努力掌握它的复杂性。 - Mike - SMT
4
这个答案特别全面,填补了我知识上的空白。向您致敬! - physincubus
你的第二个“本地Python”示例与第一个示例(以及基于super的解决方案)的行为不同。使用hasattr(next_class, '__init__')可能会隐含地指示存在不必要的方法。例如,想象一下规范的钻石层次结构class A(): ...; class B(A): ...; class C(A): ...; class D(B, C): ...,其中AC有自己的__init__方法,而D具有如所述示例中的__init__。然后调用D().__init__()将导致调用A.__init__而不是C.__init__ - facehugger
也许使用 if '__init__' in next_class.__dict__: 更好。 - facehugger
显示剩余2条评论

282
在Python 3.0+中,可以使用以下代码:
super().__init__()

为了使你的调用简洁,不需要显式地引用父级或类名,这可能很方便。我只想补充一点,对于Python 2.7或以下版本,有些人通过编写self.__class__而不是类名来实现名称不区分大小写的行为。

super(self.__class__, self).__init__()  # DON'T DO THIS!

但是,这会破坏继承你的类的任何类对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 的构造函数已经足够好了,但出于某种原因,我想实现一个正方形,以便重新实现一些其他方法。
当我使用 mSquare = Square('a', 10,10) 创建一个Square时,Python 调用 Rectangle 的构造函数,因为我没有给 Square 自己的构造函数。然而,在 Rectangle 的构造函数中,调用 super(self.__class__,self) 将返回 mSquare 的超类,所以它再次调用 Rectangle 的构造函数。这就是无限循环发生的方式,正如 @S_C 所提到的那样。在这种情况下,当我运行 super(...).__init__() 时,我正在调用 Rectangle 的构造函数,但由于我没有给它任何参数,所以会出现错误。

46
这个答案的意思是,如果您再次子类化而不提供新的__init__,那么 super(self.__class__, self).__init__() 将不起作用并导致无限递归。 - glglgl
26
这个回答太荒谬了。如果你要这样滥用super,那么最好硬编码基类的名称,这比现在做法更正确。使用super的第一个参数的整个意义就是它不一定是self的类型。请阅读rhettinger的"super considered super"(或观看他的一些视频)。 - Veky
7
这里展示的Python 2快捷方式存在已经提到的缺陷。不要使用它,否则你的代码会以你无法预测的方式崩溃。这个“方便的快捷方式”会破坏super,但你可能在沉迷于调试中很长一段时间后才意识到它。如果super过于冗长,请使用Python 3。 - Ryan Hiebert
2
@Tino,我不太同意你的编辑。同时说某事物是不可能做到的和不应该这样做是没有意义的——我在我的帖子中描述的是可以做到的,但出于我所列举的原因,这是一个不好的想法。 - AnjoMan
6
告诉某人可以做一些明显是错误的事情是毫无意义的。 你可以将“echo”别名为“python”。 没有人会建议这样做! - Russia Must Remove Putin

84

Super没有副作用

Base = ChildB

Base()

正常工作

Base = ChildA

Base()

会进入无限递归。


8
在这种情况下,“Super没有副作用”的说法是没有意义的。Super只是确保我们按照方法解析顺序调用正确的下一个类方法,而另一种方式硬编码下一个要调用的方法,使得协同多重继承更加困难。 - Russia Must Remove Putin
6
这个答案只包含片段(仅代码示例在回答中才有意义)。 - MarkHu

77

提醒一下...使用Python 2.7,以及自从版本2.2引入super()以来,只有在其中一个父类继承了最终继承object新式类),才能调用super()

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


5
非常好的观点。如果你没有明确提到: class Base(object): 那么你将会收到这样的错误信息: "TypeError: must be type, not classobj" - andilabs

62

实际上并没有什么区别。 super()会查找MRO(方法解析顺序,使用cls.__mro__访问)中的下一个类来调用方法。只调用基础__init__将调用基础__init__。恰好MRO只有一个项目--基础。因此,您正在以更好的方式使用super()进行相同的操作(特别是如果您稍后要进行多重继承)。


3
我明白了。您能详细解释一下为什么在多重继承中使用super()更好吗?对我来说,base.init(self)更短(更简洁)。如果我有两个基类,那么就需要两行这样的代码,或者两行super()代码。或者我误解了您所说的“更好”是什么意思? - Mizipzor
8
实际上,只需要一条super()语句。当你有多个继承时,方法解析顺序(MRO)仍然是扁平化的。因此,第一个super().__init__调用会调用下一个类的__init__,然后再依次调用下一个类的__init__。你应该真正地查阅一些相关的文档。 - Devin Jeanpierre
子类的MRO也包含了object - 一个类的MRO可以在__mro__类变量中被访问到。 - James Brady
2
还要注意,经典类(2.2之前的版本)不支持super - 您必须明确引用基类。 - James Brady
子类MRO也包含对象 - 类的MRO在__mro__类变量中可见。这是一个大错误。哎呀。 - Devin Jeanpierre
假设“MRO只有一个项目”意味着ChildB永远不会被子类化是不正确的。实际上,直到运行时调用super()时才知道MRO将是什么。 - nispio

44
主要区别在于`ChildA.__init__`会无条件调用`Base.__init__`,而`ChildB.__init__`会调用`__init__`在`self`祖先线路上的任何类都可能是`ChildB`的祖先(这可能与您的预期不同)。
如果添加一个使用多重继承的`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 Method Resolution Order is ChildC->ChildB->Mixin->Base

那么对于ChildC的实例而言,Base不再是ChildB的父类。现在,如果selfChildC的实例,super(ChildB,self)将会指向Mixin

你已经在ChildBBase之间插入了Mixin。你可以利用它来使用super()

因此,如果你设计的类可以在协同多重继承的场景中使用,你可以使用super,因为你无法在运行时确定谁将会是祖先。

super considered super postpycon 2015 accompanying video 很好地解释了这一点。


3
这个问题的关键在于super(ChildB, self)的含义取决于self所引用的对象的MRO,而这个MRO直到运行时才能确定。换句话说,除非能保证ChildB永远不会被子类化,否则ChildB的作者无法知道super()在所有情况下都会解析为什么。 - nispio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接