了解Python super()与\uuu init\uu()方法

2024-04-16 07:04:32 发布

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

我试图理解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()

Tags: self用法baseobjectinitdef孩子子类
3条回答

super()允许您避免显式地引用基类,这很好。但是主要的优势在于多重继承,在这里可以发生各种各样的fun stuff。如果还没有,请参阅standard docs on super

注意the syntax changed in Python 3.0:你可以直接说super().__init__(),而不是super(ChildB, self).__init__(),这在IMO是相当好的。标准文档还引用了一个guide to using ^{},这是很有解释性的。

我们注意到在Python3.0+中可以使用

super().__init__()

进行调用,这是简洁的,不需要显式引用父名称或类名,这很方便。我只想补充一点,对于Python2.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的构造函数已经足够好了,但是无论出于什么原因,我都想实现一个正方形,以便可以重新实现其他一些方法。

当我使用mSquare = Square('a', 10,10)创建Square时,Python调用Rectangle的构造函数,因为我没有给Square自己的构造函数。但是,在Rectangle的构造函数中,调用super(self.__class__,self)将返回mSquare的超类,因此它再次调用Rectangle的构造函数。这就是无限循环的发生方式,正如@S\C所提到的,在这种情况下,当我运行super(...).__init__()时,我正在调用Rectangle的构造函数,但是由于我没有给它任何参数,我将得到一个错误。

I'm trying to understand super()

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

在Python 3中,我们可以这样称呼它:

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

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

super(ChildB, self).__init__()

如果没有super,您使用多重继承的能力将受到限制:

Base.__init__(self) # Avoid this.

我在下面进一步解释。

"What difference is there actually in this code?:"

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得到一个间接层,它使用当前类来确定要在MRO中查找的下一个类的__init__

我在canonical question, How to use 'super' in Python?的一个答案中说明了这种差异,它演示了依赖注入和合作多重继承。

如果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对象,我们将不得不在任何地方编写此手动代码(或者重新创建它!)以确保在方法解析顺序中调用正确的下一个方法!

super如何在Python 3中做到这一点,而不被明确告知它是从哪个类和实例调用的?

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

因为它需要MRO的第一个参数using ^{} with static methods is impossible

对其他答案的批评:

super() lets you avoid referring to the base class explicitly, which can be nice. . But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.

这是一个很难理解的问题,但是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__()

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

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

现在记住,ChildB使用super,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'

相关问题 更多 >