使用superclass .__ init()__而不是superclass()

时间:2018-07-04 21:40:37

标签: python python-3.x inheritance

我是Python的初学者,并且使用Lutz的书来理解Python的OOPS。这个问题可能是基本的,但我将不胜感激。我研究了SO,找到了“如何”的答案,但没有找到“为什么”的答案。

据我从书中了解,如果Sub继承了Super,则不必调用超类(Super的)__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__而不是直接致电{{1 }}?

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

__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)上。我将不胜感激。


作为参考,这是根据我对SO的研究得出的代码的工作版本:

name

PS:我正在Anaconda发行版下使用Sub

3 个答案:

答案 0 :(得分:2)

  

据我从书中了解到,如果Sub继承了Super,则不必调用超类(Super的)__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's __init__Super.__init__(self, name)而不是直接调用super(Sub, self).__init__(name)来呼叫Super(name)

因为这些功能有很大不同。

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

特别是,Super.__init__确实会被调用一次,但是调用它的self是新的Super实例,您只是要扔掉它,在Super(name)情况下,而在self情况下则是您自己的super(Sub, self).__init__(name)

因此,在第一种情况下,它在其他被丢弃的对象上设置了name属性,却没有人在您的对象上设置它,这就是self.name之后会引发{{ 1}}。

如果您在两个类的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)

现在我们有两个不同的对象,它们的地址分别为0x10f7a9ea0和0x10f7a9ec0,并且类型不同。


如果您对幕后的魔力感到好奇,Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0> Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0> 会做类似的事情(简化了一点,并跳过了一些步骤 1 ):

Super(name)

…而_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),则可能是为Python 2编写的过时的书。

在Python 3中,您只需执行以下操作:

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

如果您想了解更多,这一切都在文档中,但这可能有点令人生畏:

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


1。这种解释中最大的弊端是Super实际上返回了一个代理对象,该代理对象的行为类似于绑定到super的{​​{1}},绑定方法的方式相同,可用于绑定方法,例如_baseclass。如果您知道方法的工作原理,那么这是很有用/有趣的知识,但如果您不知道方法的话,可能只会造成额外的困惑。

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

答案 1 :(得分:1)

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

Super.__init__接受未初始化的Super实例并将其初始化。 Super 创建并初始化一个与您要初始化的实例完全独立的新实例(然后立即将新实例扔掉)。您要初始化的实例未更改。

答案 2 :(得分:1)

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将继承设置为:{{1} }从SubSub继承,而SubSub2继承,Superobject继承。

您可以测试:

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