理解 Python super() 与 __init__() 方法 [重复]

2024-11-20 08:44:00
admin
原创
7
摘要:问题描述:为何super()使用?Base.__init__使用和之间有区别吗super().__init__?class Base(object): def __init__(self): print "Base created" class C...

问题描述:

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

解决方案 1:

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

请注意,Python 3.0 中的语法发生了变化:您可以直接使用 whichsuper().__init__()来代替,在super(ChildB, self).__init__()我看来,这要好得多。标准文档还引用了使用super()which 的指南,这很有说明性。

解决方案 2:

我试图理解super()

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

在Python3中我们可以这样调用:

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

在 Python 2 中,我们需要super使用定义类的名称和进行这样的调用self,但是从现在开始我们将避免这样做,因为它是多余的、更慢的(由于名称查找)和更冗长的(因此,如果您还没有更新您的 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__()

此代码的主要区别在于,在withChildB中获得了一个间接层,它使用它定义的类来确定下一个要在 MRO 中查找的类。__init__`super`__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 个反对票,回答者仍然坚持提出该答案,直到一位好心的编辑解决了他的问题。)

解释:使用self.__class__代替显式传递类名将super()导致递归。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()无参数调用方法让我们避开了这个问题。

解决方案 3:

值得注意的是,在 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已经足够好了,但无论出于什么原因,我想实现一个 Square,以便我可以重新实现其他方法。

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

解决方案 4:

Super 没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

陷入无限递归。

解决方案 5:

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

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

解决方案 6:

实际上没有。super()查看 MRO(方法解析顺序,使用 访问cls.__mro__)中的下一个类来调用方法。只需调用基类__init__就会调用基类__init__。实际上,MRO 只有一个项目——基类。因此,您实际上是在做完全相同的事情,但使用 的方式更好super()(特别是如果您以后进入多重继承)。

解决方案 7:

主要区别在于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 Method Resolution Order is ChildC->ChildB->Mixin->Base

thenBase不再是ChildBforChildC实例的父级。现在super(ChildB, self)将指向Mixinifself是一个ChildC实例。

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

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

这篇经过深思熟虑的超级帖子和pycon 2015 附带视频很好地解释了这一点。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用