简体   繁体   English

使用superclass .__ init()__而不是superclass()调用超类构造函数的原因

[英]Reason for calling super class constructor using superclass.__init()__ instead of superclass()

I am a beginner in Python and using Lutz's book to understand OOPS in Python. 我是Python的初学者,并且使用Lutz的书来理解Python中的OOPS。 This question might be basic, but I'd appreciate any help. 这个问题可能是基本的,但我将不胜感激。 I researched SO and found answers on "how", but not "why." 我研究了SO,找到了“如何”的答案,但没有找到“为什么”的答案。

As I understand from the book, if Sub inherits Super then one need not call superclass' ( Super 's) __init__() method. 当我从书上知道,如果Sub继承了Super然后一个不需要调用父类的( Super的) __init__()方法。

Example: 例:

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

class Sub(Super):
    pass

a = Sub("Harry")
a.name

Above code does assign attribute name to the object a . 上面的代码确实将属性name分配给对象a It also prints the name as expected. 它还会按预期打印name

However, if I modify the code as: 但是,如果我将代码修改为:

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

The above code doesn't work fine. 上面的代码无法正常工作。 By fine, I mean that although Super.__init__() does get called (as seen from the print statements), there is no attribute attached to a . 通过罚款,我的意思是,虽然Super.__init__()不会被调用(从打印语句看到的),没有连接到属性a When I run a.name , I get an error, AttributeError: 'Sub' object has no attribute 'name' 当我运行a.name ,出现错误, AttributeError: 'Sub' object has no attribute 'name'

I researched this topic on SO, and found the fix on Chain-calling parent constructors in python and Why aren't superclass __init__ methods automatically invoked? 我在SO上对此主题进行了研究,并找到了有关在python中调用链调用父构造函数的修复程序,以及为什么超类__init__方法不会自动调用吗?

These two threads talk about how to fix it, but they don't provide a reason for why. 这两个线程讨论如何修复它,但是没有提供原因。

Question: Why do I need to call Super 's __init__ using Super.__init__(self, name) OR super(Sub, self).__init__(name) instead of a direct call Super(name) ? 问题: 为什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name)而不是直接调用Super(name)来调用Super__init__

In Super.__init__(self, name) and Super(name) , we see that Super's __init__() gets called, (as seen from print statements), but only in Super.__init__(self, name) we see that the attribute gets attached to Sub class. Super.__init__(self, name)Super(name) ,我们看到调用了Super的__init__() (从打印语句中看到),但是仅在Super.__init__(self, name)我们看到了属性附属于Sub类。

Wouldn't Super(name) automatically pass self (child) object to Super ? Super(name)不会自动将self (子)对象传递给Super吗? Now, you might ask that how do I know that self is automatically passed? 现在,您可能会问我如何知道self被自动传递? If I modify Super(name) to Super(self,name) , I get an error message that TypeError: __init__() takes 2 positional arguments but 3 were given . 如果将Super(name)修改为Super(self,name) ,则会收到一条错误消息,指出TypeError: __init__() takes 2 positional arguments but 3 were given As I understand from the book, self is automatically passed. 据我了解, self是自动传递的。 So, effectively, we end up passing self twice. 因此,有效地,我们最终两次通过self

I don't know why Super(name) doesn't attach name attribute to Sub even though Super.__init__() is run. 我不知道为什么即使Super.__init__()运行, Super(name)也不将name属性附加到Sub I'd appreciate any help. 我将不胜感激。


For reference, here's the working version of the code based on my research from SO: 作为参考,以下是根据我对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: I am using Python-3.6.5 under Anaconda Distribution. PS:我在Anaconda发行版下使用Python-3.6.5

As I understand from the book, if Sub inherits Super then one need not call superclass' ( Super 's) __init__() method. 当我从书上知道,如果子继承了超级然后一个不需要调用父类的( Super的) __init__()方法。

This is misleading. 这是误导。 It's true that you aren't required to call the superclass's __init__ method—but if you don't, whatever it does in __init__ never happens. 的确,您不需要调用超类的__init__方法,但是,如果不这样做,则__init__任何操作都不会发生。 And for normal classes, all of that needs to be done. 对于普通班级,所有这些都需要完成。 It is occasionally useful, usually when a class wasn't designed to be inherited from, like this: 有时很有用,通常是在未将类设计为继承自此类的情况下,如下所示:

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

Imagine that you want all the behavior of this class, but with a string rather than a file. 想象一下,您需要此类的所有行为,但要使用字符串而不是文件。 The only way to do that is to skip the open : 唯一的方法是跳过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)

Here, we wanted to avoid the self.file assignment in the superclass. 在这里,我们要避免在超类中分配self.file In your case—as with almost all classes you're ever going to write—you don't want to avoid the self.name assignment in the superclass. 在你的情况,因为几乎所有的类你曾经打算写你想要避免self.name在超分配。 That's why, even though Python allows you to not call the superclass's __init__ , you almost always call it. 这就是为什么即使Python 允许您不调用超类的__init__ ,您也几乎总是调用它的原因。

Notice that there's nothing special about __init__ here. 注意,这里的__init__没有什么特别的。 For example, we can override dostuff to call the base class's version and then do extra stuff: 例如,我们可以重写dostuff以调用基类的版本,然后执行其他操作:

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

… or we can override close and intentionally not call the base class: …或我们可以重写close并有意不调用基类:

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

The only difference is that good reasons to avoid calling the base class tend to be much more common in normal methods than in __init__ . 唯一的区别是,避免调用基类的充分理由在普通方法中比在__init__更常见。


Question: Why do I need to call Super's __init__ using Super.__init__(self, name) OR super(Sub, self).__init__(name) instead of a direct call Super(name) ? 问题:为什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name)而不是直接调用Super(name)来调用Super's __init__

Because these do very different things. 因为这些功能有很大不同。

Super(name) constructs a new Super instance, calls __init__(name) on it, and returns it to you. Super(name)构造一个新的Super实例,在其上调用__init__(name) ,并将其返回给您。 And you then ignore that value. 然后,您将忽略该值。

In particular, Super.__init__ does get called one time either way—but the self it gets called with is that new Super instance, that you're just going to throw away, in the Super(name) case, while it's your own self in the super(Sub, self).__init__(name) case. 特别是, Super.__init__确实会被调用一次,但是调用它的self是新的Super实例,在Super(name)情况下,您只是将其丢弃,而它是您自己的selfsuper(Sub, self).__init__(name)情况下。

So, in the first case, it sets the name attribute on some other object that gets thrown away, and nobody ever sets it on your object, which is why self.name later raises an AttributeError . 因此,在第一种情况下,它将在其他一些被丢弃的对象上设置name属性,而没有人在您的对象上设置它,这就是为什么self.name稍后引发AttributeError

It might help you understand this if you add something to both class's __init__ methods to show which instance is involved: 如果您在两个类的__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.

If that last line is super().__init__(name) , super(Sub, self).__init__name) , or Super.__init__(self, name) , you will see something like this: 如果最后一行是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>

Notice that it's the same object, the Sub at address 0x10f7a9e80, in both cases. 请注意,在两种情况下,它都是同一个对象,即地址为0x10f7a9e80的Sub

… but if that last line is Super(name) : …但是如果最后一行是Super(name)

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

Now we have two different objects, at different addresses 0x10f7a9ea0 and 0x10f7a9ec0, and with different types. 现在,我们有两个不同的对象,它们的地址分别为0x10f7a9ea0和0x10f7a9ec0,并且类型不同。


If you're curious about what the magic all looks like under the covers, Super(name) does something like this (oversimplifying a bit and skipping over some steps 1 ): 如果您对所有魔术的外观都感到好奇,那么Super(name)执行以下操作(简化一些,并跳过一些步骤1 ):

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

… while super(Sub, self).__init__(name) does something like this: …而super(Sub, self).__init__(name)则是这样的:

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

As a side note, if a book is telling you to use super(Sub, self).__init__(name) or Super.__init__(self, name) , it's probably an obsolete book written for Python 2. 附带说明一下,如果一本书告诉您使用super(Sub, self).__init__(name)Super.__init__(self, name) ,则可能是为Python 2写的过时的书。

In Python 3, you just do this: 在Python 3中,您只需执行以下操作:

  • super().__init__(name) : Calls the correct next superclass by method resolution order. super().__init__(name) :按方法解析顺序调用正确的下一个超类。 You almost always want this. 您几乎总是想要这个。
  • super(Sub, self).__init__(name) : Calls the correct next superclass—unless you make a mistake and get Sub wrong there. super(Sub, self).__init__(name) :调用正确的下一个超类-除非您犯了一个错误并在那里使Sub错误。 You only need this if you're writing dual-version code that has to run in 2.7 as well as 3.x. 仅当您要编写必须在2.7和3.x中运行的双版本代码时,才需要此代码。
  • Super.__init__(self, name) : Calls Super , whether it's the correct next superclass or not. Super.__init__(self, name) :调用Super ,无论它是否是正确的下一个超类。 You only need this if the method resolution order is wrong and you have to work around it. 仅当方法解析顺序错误并且需要解决时,才需要此方法。 2 2

If you want to understand more, it's all in the docs, but it can be a bit daunting: 如果您想了解更多,这些都在文档中,但这可能有点令人生畏:

The original introduction to super , __new__ , and all the related features was very helpful to me in understanding all of this. super__new__以及所有相关功能原始介绍对我理解所有这些非常有帮助。 I'm not sure if it'll be as helpful to someone who's not coming at this already understanding old-style Python classes, but it's pretty well written, and Guido (obviously) knows what he's talking about, so it might be worth reading. 我不确定这是否会对那些已经不了解老式Python类的人有帮助,但是它写得不错,Guido(显然)知道他在说什么,所以值得一读。


1. The biggest cheat in this explanation is that super actually returns a proxy object that acts like _baseclass bound to self in the same way methods are bound, which can be used to bind methods, like __init__ . 1.这种解释中最大的_baseclass是, super实际上返回了一个代理对象,该代理对象的行为类似于_baseclass绑定到self ,其方式与方法绑定的方式相同,可用于绑定诸如__init__方法。 This is useful/interesting knowledge if you know how methods work, but probably just extra confusion if you don't. 如果您知道方法的工作原理,那么这是有用的/有趣的知识,但如果您不知道方法的话,可能只是额外的困惑。

2. … or if you're working with old-style classes, which don't support super (or proper method-resolution order). 2.…或如果您正在使用不支持super (或正确的方法解析顺序)的旧式类。 This never comes up in Python 3, which doesn't have old-style classes. 没有老式类的Python 3永远不会出现这种情况。 But, unfortunately, you will see it in lots of tkinter examples, because the best tutorial is still Effbot's, which was written for Python 2.3, when Tkinter was all old-style classes, and has never been updated. 但是,不幸的是,您会在许多tkinter示例中看到它,因为最好的教程仍然是Effbot,它是为Python 2.3编写的,当时Tkinter都是老式类,并且从未进行过更新。

Super(name) is not a "direct call" to the superclass __init__ . Super(name)不是对超类__init__的“直接调用”。 After all, you called Super , not Super.__init__ . 毕竟,您叫Super ,而不是Super.__init__

Super.__init__ takes an uninitialized Super instance and initializes it. Super.__init__接受未初始化的Super实例并对其进行初始化。 Super creates and initializes a new, completely separate instance from the one you wanted to initialize (and then you immediately throw the new instance away). Super 创建并初始化了一个与要初始化的实例完全独立的新实例(然后立即将新实例扔掉了)。 The instance you wanted to initialize is untouched. 您要初始化的实例未更改。

Super(name) instantiates a new instance of super. Super(name)实例化super的新实例。 Think of this example: 想想这个例子:

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

In order to explicitly call The Super 's constructor on the current instance, you'd have to use the following syntax: 为了在当前实例上显式调用Super的构造函数,您必须使用以下语法:

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

Now, maybe you don't want read further if you are a beginner. 现在,如果您是初学者,也许您不想进一步阅读。

If you do, you will see that there is a good reason to use super(Sub, self).__init__(name) (or super().__init__(name) in Python 3) instead of Super.__init__(self, name) . 如果这样做,您会发现有充分的理由使用super(Sub, self).__init__(name) (或Python 3中的super().__init__(name) )而不是Super.__init__(self, name)


Super.__init__(self, name) works fine, as long as you are certain that Super is in fact your superclass. 只要您确定Super实际上是您的超类Super.__init__(self, name)可以正常工作。 But in fact, you don't know ever that for sure. 但实际上,您永远都不知道。

You could have the following code: 您可能具有以下代码:

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

You would now expect that SubSub() ends up calling all of the above constructors, but it does not: 您现在可以期望SubSub()最终调用上述所有构造函数,但不会:

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

To correct it, you'd have to do: 要更正它,您必须执行以下操作:

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

Now it works: 现在可以正常工作:

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

The reason is that the super class of Sub is declared to be Super , in case of multiple inheritance in class SubSub , Python's MRO establishes the inheritance as: SubSub inherits from Sub , which inherits from Sub2 , which inherits from Super , which inherits from object . 原因是Sub SubSub类被声明为Super ,如果SubSub类中有多个继承,Python的MRO将继承建立为: SubSub继承自SubSub继承自Sub2Sub2继承自SuperSuper继承自object

You can test that: 您可以测试:

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

Now, the super() call in constructors of each of the classes finds the next class in the MRO so that the constructor of that class can be called. 现在,每个类的构造函数中的super()调用都会在MRO中找到下一个类,以便可以调用该类的构造函数。

See https://www.python.org/download/releases/2.3/mro/ 参见https://www.python.org/download/releases/2.3/mro/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM