python - super用法 - super__ init__ kw




用__init__()方法理解Python super() (5)

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

我在规范问题的答案中说明了这种差异,如何在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对象,我们必须在任何地方编写这个手动代码(或重新创建它!)以确保我们在方法解析顺序中调用正确的下一个方法!

超级如何在没有明确告知调用它的方法中的哪个类和实例的情况下在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使用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我们在子实例的方法中,那么它将在行中查找下一个方法(可能是这个)导致递归,可能导致逻辑失败(在回答者的例子中,它确实)或者当运行错误时超出递归深度。

>>> 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'

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

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


使用Python 2.7,我相信自从版本2.2中引入了super()如果其中一个父级继承自最终继承object的类( 新式super()则只能调用super() )。

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


已经注意到在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,所以我可以重新实现其他方法。

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


超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。





super