【问题标题】:Reason for calling super class constructor using superclass.__init()__ instead of superclass()使用 superclass.__init()__ 而不是 superclass() 调用超类构造函数的原因
【发布时间】:2018-12-13 07:54:41
【问题描述】:

我是 Python 初学者,使用 Lutz 的书了解 Python 中的 OOPS。这个问题可能是基本的,但我会很感激任何帮助。我研究了 SO 并找到了关于“如何”的答案,但没有找到“为什么”的答案。

据我从书中了解到,如果Sub 继承Super,则无需调用超类'(Super's)__init__() 方法。

示例:

class Super:
    def __init__(self,name):
        self.name=name
        print("Name is:",name)

class Sub(Super):
    pass

a = Sub("Harry")
a.name

以上代码确实将属性name 分配给对象a。它还按预期打印name

但是,如果我将代码修改为:

class Super:
    def __init__(self,name):
        print("Inside Super __init__")
        self.name=name
        print("Name is:",name)

class Sub(Super):
      def __init__(self,name):
          Super(name) #Call __init__ directly

a = Sub("Harry")
a.name

上面的代码不能正常工作。好的,我的意思是虽然Super.__init__() 确实被调用(从打印语句中可以看出),但a 没有附加属性。当我运行a.name 时,我得到一个错误,AttributeError: 'Sub' object has no attribute 'name'

我在 SO 上研究了这个主题,并在 Chain-calling parent constructors in pythonWhy aren't superclass __init__ methods automatically invoked? 上找到了解决方法

这两个线程讨论了如何修复它,但没有提供原因。

问题:为什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name) 来调用Super__init__,而不是直接调用Super(name)

Super.__init__(self, name)Super(name) 中,我们看到Super 的__init__() 被调用(从打印语句中可以看出),但仅在Super.__init__(self, name) 中,我们看到该属性附加到Sub 类。

Super(name) 不会自动将self(子)对象传递给Super?现在,你可能会问,我怎么知道self 是自动通过的?如果我将Super(name) 修改为Super(self,name),我会收到TypeError: __init__() takes 2 positional arguments but 3 were given 的错误消息。据我从书中了解到,self 是自动通过的。所以,实际上,我们最终通过了self 两次。

我不知道为什么Super(name) 没有将name 属性附加到Sub,即使Super.__init__() 已运行。我会很感激任何帮助。


作为参考,这是基于我对 SO 的研究的代码的工作版本:

class Super:
    def __init__(self,name):
        print("Inside __init__")
        self.name=name
        print("Name is:",name)

class Sub(Super):
    def __init__(self,name):
        #Super.__init__(self, name) #One way to fix this
        super(Sub, self).__init__(name) #Another way to fix  this

a = Sub("Harry")
a.name

PS:我在 Anaconda Distribution 下使用Python-3.6.5

【问题讨论】:

  • Super(name) 创建一个 new 实例 Super 立即被丢弃。您需要在特定实例(self)上调用__init__(),首选super(),您不再需要将参数传递给super(),例如super().__init__(name) 就足够了。
  • 在您的类Sub 中,您正在初始化父类的新实例,这不是您想要做的。 Subclass 想要初始化父类的所有属性/方法(继承)(请参阅 abarnert 的答案以了解操作方法)。因为你没有初始化父类的类属性,所以你没有给你的子实例(你称之为a)属性name。而您正在尝试访问一个不存在的属性,这就是引发错误的原因。

标签: python python-3.x inheritance


【解决方案1】:

据我了解,如果 Sub 继承 Super,则无需调用超类'(Super's)__init__() 方法。

这是误导。确实要求调用超类的__init__ 方法,但如果你不这样做,__init__ 中的任何操作都不会发生。对于普通课程,所有这些都需要完成。它偶尔很有用,通常是当一个类没有被设计为继承自,像这样:

class Rot13Reader:
    def __init__(self, filename):
        self.file = open(filename):
    def close(self):
        self.file.close()
    def dostuff(self):
        line = next(file)
        return codecs.encode(line, 'rot13')

想象一下,您想要这个类的所有行为,但使用的是字符串而不是文件。唯一的方法是跳过open

class LocalRot13Reader(Rot13Reader):
    def __init__(self, s):
        # don't call super().__init__, because we don't have a filename to open
        # instead, set up self.file with something else
        self.file = io.StringIO(s)

在这里,我们想避免超类中的self.file 赋值。在你的情况下——就像你将要编写的几乎所有类一样——你想避免超类中的self.name 赋值。这就是为什么,即使 Python允许你不调用超类的 __init__,你几乎总是调用它。

请注意,__init__ 没有什么特别之处。例如,我们可以重写dostuff 来调用基类的版本,然后做一些额外的事情:

def dostuff(self):
    result = super().dostuff()
    return result.upper()

… 或者我们可以覆盖close 并且故意不调用基类:

def close(self):
    # do nothing, including no super, because we borrowed our file

唯一的区别是,避免调用基类的充分理由在普通方法中比在__init__ 中更常见。


问题:为什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name) 来调用Super's __init__ 而不是直接调用Super(name)

因为它们做的事情非常不同。

Super(name) 构造一个新的Super 实例,在其上调用__init__(name),并将其返回给您。然后你会忽略这个值。

特别是,Super.__init__ 确实会被调用一次,但它被调用的 self 是新的 Super 实例,在 Super(name) 的情况下,你将要丢弃它,而在 super(Sub, self).__init__(name) 案例中是您自己的 self

因此,在第一种情况下,它会在其他一些被丢弃的对象上设置name 属性,而没有人会在您的对象上设置它,这就是为什么self.name 稍后会引发AttributeError

如果您在两个类的 __init__ 方法中添加一些内容以显示涉及哪个实例,可能会帮助您理解这一点:

class Super:
    def __init__(self,name):
        print(f"Inside Super __init__ for {self}")
        self.name=name
        print("Name is:",name)

class Sub(Super):
    def __init__(self,name):
        print(f"Inside Sub __init__ for {self}")
        # line you want to experiment with goes here.

如果最后一行是super().__init__(name)super(Sub, self).__init__name)Super.__init__(self, name),您将看到如下内容:

Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>

请注意,在这两种情况下,它都是同一个对象,地址为 0x10f7a9e80 的 Sub

…但如果最后一行是Super(name):

Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>

现在我们有两个不同的对象,位于不同的地址 0x10f7a9ea0 和 0x10f7a9ec0,并且具有不同的类型。


如果您对幕后的所有魔法感到好奇,Super(name) 会这样做(过于简单化并跳过一些步骤1):

_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
    Super.__init__(_newobj, name)

…而super(Sub, self).__init__(name) 做了这样的事情:

_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)

作为旁注,如果一本书告诉你使用 super(Sub, self).__init__(name)Super.__init__(self, name),那么它可能是一本为 Python 2 编写的过时的书。

在 Python 3 中,您只需这样做:

  • super().__init__(name):按方法解析顺序调用正确的下一个超类。你几乎总是想要这个。
  • super(Sub, self).__init__(name):调用正确的下一个超类——除非你犯了一个错误并在那里得到Sub 错误。仅当您编写必须在 2.7 和 3.x 中运行的双版本代码时才需要它。
  • Super.__init__(self, name):调用Super,不管它是否是正确的下一个超类。只有在方法解析顺序错误并且您必须解决它时才需要它。2

如果您想了解更多信息,请参阅文档,但可能有点令人生畏:

original introduction to super, __new__, and all the related features 对我理解这一切非常有帮助。我不确定它是否对那些不了解这个已经理解旧式 Python 类的人有帮助,但它写得很好,而且 Guido(显然)知道他在说什么,所以它可能值得一读.


1。这个解释中最大的骗局是super实际上返回了一个代理对象,其行为类似于_baseclass绑定到self,其方式与绑定方法相同,可以用于绑定方法,例如__init__。如果你知道方法是如何工作的,这是有用/有趣的知识,但如果你不知道,这可能只是额外的混乱。

2。 … 或者如果您正在使用不支持super(或正确的方法解析顺序)的旧式类。这在没有旧式类的 Python 3 中从未出现过。但是,不幸的是,你会在很多 tkinter 示例中看到它,因为最好的教程仍然是 Effbot 的,它是为 Python 2.3 编写的,当时 Tkinter 都是老式类,从未更新过。

【讨论】:

  • 感谢您的出色回应。在第一个示例中,LocalRot13Reader 没有继承自 Rot13Reader。所以,我相信这个例子与我发布的不同。
  • @watchtower 哎呀;这是一个错字。感谢您抓住它,我会修复它。如果添加继承,一切正常——初始化LocalRot13Reader 会跳过open 调用,但closedostuff 会按预期继承。
【解决方案2】:

Super(name) 不是对超类__init__ 的“直接调用”。毕竟,你打电话给Super,而不是Super.__init__

Super.__init__ 接受一个未初始化的Super 实例并对其进行初始化。 Super 创建 并初始化一个与您要初始化的实例完全不同的新实例(然后您立即将新实例丢弃)。您要初始化的实例未受影响。

【讨论】:

  • 感谢您的帮助。 Super(name) 不会也附加self(即对象a)吗?传递给 Super 的隐式 self 会发生什么?
  • 一个新的self,它传递给Super.__init__(),它被返回,但你没有将Super(name)的返回值分配给一个值,所以它会立即被丢弃。
  • @watchtower:整个程序中不只有一个selfSuper(name) 创建一个新的Super 实例以用作self
【解决方案3】:

Super(name) 实例化一个新的 super 实例。想想这个例子:

def __init__(self, name):
    x1 = Super(name)
    x2 = Super("some other name")
    assert x1 is not self
    assert x2 is not self

为了在当前实例上显式调用Super 的构造函数,您必须使用以下语法:

def __init__(self, name):
    Super.__init__(self, name)

现在,如果您是初学者,也许您不想进一步阅读。

如果这样做,您会发现有充分的理由使用super(Sub, self).__init__(name)(或Python 3 中的super().__init__(name))而不是Super.__init__(self, name)


Super.__init__(self, name) 工作正常,只要您确定 Super 实际上是您的超类。但事实上,你并不确定。

你可以有以下代码:

class Super:
    def __init__(self):
        print('Super __init__')

class Sub(Super):
    def __init__(self):
        print('Sub __init__')
        Super.__init__(self)

class Sub2(Super):
    def __init__(self):
        print('Sub2 __init__')
        Super.__init__(self)

class SubSub(Sub, Sub2):
    pass

您现在会认为 SubSub() 最终会调用所有上述构造函数,但它不会:

>>> x = SubSub()
Sub __init__
Super __init__
>>>

要纠正它,你必须这样做:

class Super:
    def __init__(self):
        print('Super __init__')

class Sub(Super):
    def __init__(self):
        print('Sub __init__')
        super().__init__()

class Sub2(Super):
    def __init__(self):
        print('Sub2 __init__')
        super().__init__()

class SubSub(Sub, Sub2):
    pass

现在可以了:

>>> x = SubSub()
Sub __init__
Sub2 __init__
Super __init__
>>>

原因是Sub的超类被声明为Super,在类SubSub多继承的情况下,Python的MRO建立继承为:SubSub继承自Sub,即继承自Sub2,后者继承自Super,后者继承自object

你可以测试一下:

>>> SubSub.__mro__
(<class '__main__.SubSub'>, <class '__main__.Sub'>, <class '__main__.Sub2'>, <class '__main__.Super'>, <class 'object'>)

现在,每个类的构造函数中的 super() 调用会在 MRO 中找到下一个类,以便可以调用该类的构造函数。

https://www.python.org/download/releases/2.3/mro/

【讨论】:

  • @abarnert 我的示例实际上具有多重继承,所以我看不出您所说的与它有何矛盾。我错过了什么吗?还是我写的答案不够清楚,不足以说明问题?
  • 您的原始答案没有 MI。您迅速对其进行了编辑以添加该内容,我赞成您的回答。也许我也应该删除我的评论?我现在就这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-18
  • 2015-06-09
  • 2014-05-01
  • 2011-04-19
  • 2013-07-19
  • 2015-06-09
相关资源
最近更新 更多