当前位置:  开发笔记 > 编程语言 > 正文

使用__init __()方法理解Python super()

如何解决《使用__init__()方法理解Pythonsuper()》经验,为你挑选了7个好方法。

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

我很想知道以下2个孩子班级之间的实际差异.

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

Kiv.. 1725

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

请注意,Python 3.0中的语法已更改:您可以说,super().__init__()而不是super(ChildB, self).__init__()哪个IMO更好一点.标准文档也引用了使用super()的指南,这是非常明确的解释.



1> Kiv..:

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

请注意,Python 3.0中的语法已更改:您可以说,super().__init__()而不是super(ChildB, self).__init__()哪个IMO更好一点.标准文档也引用了使用super()的指南,这是非常明确的解释.


你能提供一个与参数一起使用的`super()`的例子吗?
你能解释一下`super(ChildB,self).__ init __()`这个,`ChildB`和`self`与super有什么关系?
@rimiro super()的语法是`super([type [,object]])`这将返回`type`的超类.所以在这种情况下,将返回"ChildB"的超类.如果省略第二个参数,则返回的超级对象是未绑定的.如果第二个参数是一个对象,那么`isinstance(object,type)`必须为true.
如果您在这里仍然感到困惑,请阅读亚伦·霍尔的回答,您将使此页面更加愉快:/sf/ask/17360801/

2> Aaron Hall..:

我想了解一下 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__with中获得了一个间接层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个downvotes,回答者坚持提出建议,直到一位善良的编辑解决了他的问题.)

说明:那个回答建议像这样调用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 "", line 1, in 
  File "", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'


我仍然需要围绕这个`super()`函数工作,但是,这个答案在深度和细节方面显然是最好的.我也非常感谢答案中的批评.通过识别其他答案中的陷阱,它还有助于更好地理解这一概念.谢谢 !
谢谢,这非常有帮助.对穷人/不当使用的批评非常说明了为什么以及如何使用超级
@Aaron Hall,感谢您提供如此详细的信息。我认为,如果导师没有提供足够的正确信息,他们应该有更多选择(至少)供导师称某些答案为不适当或不完整。

3> AnjoMan..:

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

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


这个答案表明,`super(self .__ class__,self).__ init __()`如果再次子类化而不提供新的`__init__`则不起作用.然后你有一个无限的递归.
这个答案很荒谬.如果您要以这种方式滥用超级,那么您也可以只对基类名称进行硬编码.这比错误少.超级的第一个论点的全部意义在于它不一定是自我的类型.请阅读rhettinger的"超级考虑超级"(或观看他的一些视频).
这里为Python 2演示的快捷方式已经存在陷阱.不要使用它,否则您的代码将以您无法预测的方式中断.这个"方便的快捷方式"打破了超级,但你可能没有意识到它,直到你沉迷了大量的时间进行调试.如果super太冗长,请使用Python 3.
没有意义的是告诉别人他们可以做一些简单地证明是不正确的事情.您可以将`echo`别名为`python`.没有人会建议它!

4> 小智..:

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归.


声明"超级没有副作用"在这种背景下没有意义.超级简单地保证我们在方法解析顺序中调用正确的下一个类的方法,而另一种方式硬编码下一个要调用的方法,这使得协作多重继承更加困难.

5> rgenito..:

使用Python 2.7,我相信自从super()2.2版本引入以来,只有super()其中一个父级继承自最终继承的类object(新式类)时才能调用.

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


非常好的一点.如果你没有明确提到:class Base(object):那么你会得到类似的错误:"TypeError:必须是type,而不是classobj"

6> Devin Jeanpi..:

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


实际上,它将是一条super()行.当你有多重继承时,MRO仍然是平的.所以第一个super().__ init__调用调用下一个类的__init__,然后调用下一个,依此类推.你应该查看一些关于它的文档.
我知道了.你能详细说明为什么使用具有多重继承的super()更好吗?对我来说,基础.__ init __(self)更短(更干净).如果我有两个基类,那将是两行,或两行()行.或者我是否误解了"更好"的含义?

7> ecerulm..:

主要的区别是,ChildA.__init__将无条件调用Base.__init__,而ChildB.__init__将调用__init__任何类恰好是ChildB祖先self的祖先的的路线 (可能与预期的不同).

如果添加ClassC使用多重继承的a:

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不再是母公司ChildBChildC实例.现在super(ChildB, self)将指出Mixinif self是一个ChildC实例.

已经插入Mixin之间ChildBBase.你可以利用它super()

因此,如果您设计了类以便可以在协作多重继承方案中使用它们,那么您可以使用它,super因为您实际上并不知道谁将成为运行时的祖先.

该超认为超级岗位和PYCON 2015年影随行解释这相当不错.

推荐阅读
我我檬檬我我186
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有