问题:为什么不自动调用超类__init__方法?

为什么Python设计人员会决定子类的__init__()方法不会__init__()像某些其他语言那样自动调用其超类的方法?Pythonic和推荐的习语真的像下面这样吗?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

Why did the Python designers decide that subclasses’ __init__() methods don’t automatically call the __init__() methods of their superclasses, as in some other languages? Is the Pythonic and recommended idiom really like the following?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

回答 0

Python __init__与其他语言的构造函数之间的关键区别在于,__init__不是构造函数:它是一个初始化程序(实际的构造函数(如果有,但是请参阅下文;-)是__new__并且再次完全不同。虽然构建所有超(,毫无疑问,这样做,你继续向下构建“之前”)显然是说你的一部分构建一个子类的实例,这显然是不适合的情况下初始化,因为在许多用例中,超类的初始化需要被跳过,更改和控制-发生在子类初始化的“中间”,如果发生的话,等等。

基本上,出于完全相同的原因,初始化程序的超类委派在Python中不是自动的,此类委派对于任何其他方法也不是自动的-请注意,那些“其他语言” 对任何其他方法都不会自动进行超类委派其他方法… 只是针对构造函数(如果适用,也应包含析构函数),正如我提到的,这不是 Python的__init__方法。(的行为__new__也很特殊,尽管实际上与您的问题没有直接关系,因为它__new__是一个奇特的构造函数,实际上并不一定要构造任何东西-可以很好地返回一个现有实例,甚至一个非实例…显然Python为您提供了很多比您要记住的“其他语言”,对机械的控制更多,它本身没有自动委派__new__!-)。

The crucial distinction between Python’s __init__ and those other languages constructors is that __init__ is not a constructor: it’s an initializer (the actual constructor (if any, but, see later;-) is __new__ and works completely differently again). While constructing all superclasses (and, no doubt, doing so “before” you continue constructing downwards) is obviously part of saying you’re constructing a subclass’s instance, that is clearly not the case for initializing, since there are many use cases in which superclasses’ initialization needs to be skipped, altered, controlled — happening, if at all, “in the middle” of the subclass initialization, and so forth.

Basically, super-class delegation of the initializer is not automatic in Python for exactly the same reasons such delegation is also not automatic for any other methods — and note that those “other languages” don’t do automatic super-class delegation for any other method either… just for the constructor (and if applicable, destructor), which, as I mentioned, is not what Python’s __init__ is. (Behavior of __new__ is also quite peculiar, though really not directly related to your question, since __new__ is such a peculiar constructor that it doesn’t actually necessarily need to construct anything — could perfectly well return an existing instance, or even a non-instance… clearly Python offers you a lot more control of the mechanics than the “other languages” you have in mind, which also includes having no automatic delegation in __new__ itself!-).


回答 1

当人们模仿“ Python禅”时,我有些尴尬,好像这是任何事情的正当理由。这是一种设计理念;特定的设计决策总是可以用更具体的术语来解释-必须如此,否则“ Zen of Python”将成为做任何事情的借口。

原因很简单:您不必以与构造基类的方式完全相似的方式构造派生类。您可能有更多的参数,更少的参数,它们的顺序可能不同或根本不相关。

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

这适用于所有派生方法,而不仅仅是__init__

I’m somewhat embarrassed when people parrot the “Zen of Python”, as if it’s a justification for anything. It’s a design philosophy; particular design decisions can always be explained in more specific terms–and they must be, or else the “Zen of Python” becomes an excuse for doing anything.

The reason is simple: you don’t necessarily construct a derived class in a way similar at all to how you construct the base class. You may have more parameters, fewer, they may be in a different order or not related at all.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

This applies to all derived methods, not just __init__.


回答 2

Java和C ++ 要求由于内存布局而调用基类构造函数。

如果您有一个BaseClass包含成员的类field1,并且创建了一个SubClass添加成员的新类field2,则的实例SubClass包含field1和的空间field2。您需要一个的构造函数BaseClass来填充field1,除非您需要所有继承的类BaseClass在其自己的构造函数中重复的初始化。如果field1是私有的,那么继承类将无法初始化field1

Python不是Java或C ++。所有用户定义类的所有实例都具有相同的“形状”。它们基本上只是可​​以在其中插入属性的字典。在完成任何初始化之前,所有用户定义类的所有实例几乎完全相同;它们只是存储尚未存储的属性的地方。

因此,对于Python子类而言,不调用其基类构造函数是很有意义的。如果需要,它可以只添加属性本身。对于层次结构中的每个类,没有为给定数目的字段保留空间,并且通过BaseClass方法中的代码添加的属性与通过方法中的代码添加的属性之间没有区别SubClass

如果像通常一样,SubClass实际上确实希望BaseClass在继续进行自己的自定义之前设置所有的不变式,那么可以调用BaseClass.__init__()(或使用)super,但这是很复杂的,并且有时会出现自己的问题。但是您不必。您可以在之前,之后或使用其他参数来执行此操作。地狱,如果你想的话,可以BaseClass.__init__完全从另一个方法而不是__init__; 调用。也许您正在进行一些奇怪的懒惰初始化操作。

Python通过保持简单而实现了这种灵活性。通过编写在__init__上设置属性的方法来初始化对象self。而已。它的行为与方法完全一样,因为它正是方法。对于必须首先完成的事情,或者如果您不执行其他操作会自动发生的事情,没有其他奇怪而又不直观的规则。它唯一需要服务的目的是成为一个在对象初始化期间执行的钩子,以设置初始属性值,而它正是这样做的。如果您希望它做其他事情,则可以在代码中显式地编写。

Java and C++ require that a base class constructor is called because of memory layout.

If you have a class BaseClass with a member field1, and you create a new class SubClass that adds a member field2, then an instance of SubClass contains space for field1 and field2. You need a constructor of BaseClass to fill in field1, unless you require all inheriting classes to repeat BaseClass‘s initialization in their own constructors. And if field1 is private, then inheriting classes can’t initialise field1.

Python is not Java or C++. All instances of all user-defined classes have the same ‘shape’. They’re basically just dictionaries in which attributes can be inserted. Before any initialisation has been done, all instances of all user-defined classes are almost exactly the same; they’re just places to store attributes that aren’t storing any yet.

So it makes perfect sense for a Python subclass not to call its base class constructor. It could just add the attributes itself if it wanted to. There’s no space reserved for a given number of fields for each class in the hierarchy, and there’s no difference between an attribute added by code from a BaseClass method and an attribute added by code from a SubClass method.

If, as is common, SubClass actually does want to have all of BaseClass‘s invariants set up before it goes on to do its own customisation, then yes you can just call BaseClass.__init__() (or use super, but that’s complicated and has its own problems sometimes). But you don’t have to. And you can do it before, or after, or with different arguments. Hell, if you wanted you could call the BaseClass.__init__ from another method entirely than __init__; maybe you have some bizarre lazy initialization thing going.

Python achieves this flexibility by keeping things simple. You initialise objects by writing an __init__ method that sets attributes on self. That’s it. It behaves exactly like a method, because it is exactly a method. There are no other strange and unintuitive rules about things having to be done first, or things that will automatically happen if you don’t do other things. The only purpose it needs to serve is to be a hook to execute during object initialisation to set initial attribute values, and it does just that. If you want it to do something else, you explicitly write that in your code.


回答 3

“显式比隐式好。” 同样的道理表明我们应该明确地写出“自我”。

最后,我认为这是有好处的-您能列举一下Java关于调用超类的构造函数的所有规则吗?

“Explicit is better than implicit.” It’s the same reasoning that indicates we should explicitly write ‘self’.

I think in in the end it is a benefit– can you recite all of the rules Java has regarding calling superclasses’ constructors?


回答 4

通常,子类具有无法传递给超类的额外参数。

Often the subclass has extra parameters which can’t be passed to the superclass.


回答 5

现在,我们有一个较长的页面描述了多重继承的情况下方法解析的顺序:http : //www.python.org/download/releases/2.3/mro/

如果自动调用了构造函数,则需要另一页至少具有相同长度的页面,以解释其发生的顺序。那将是地狱…

Right now, we have a rather long page describing the method resolution order in case of multiple inheritance: http://www.python.org/download/releases/2.3/mro/

If constructors were called automatically, you’d need another page of at least the same length explaining the order of that happening. That would be hell…


回答 6

为了避免混淆,知道__init__()child_class没有__init__()类时可以调用base_class 方法是很有用的。

例:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

实际上,__init__()当在子类中找不到它时,python中的MRO会在父类中查找。如果子类中已经有一个__init__()方法,则需要直接调用父类的构造函数。

例如,以下代码将返回错误:class parent:def init(self,a = 1,b = 0):self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

To avoid confusion it is useful to know that you can invoke the base_class __init__() method if the child_class does not have an __init__() class.

Example:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

In fact the MRO in python will look for __init__() in the parent class when can not find it in the children class. You need to invoke the parent class constructor directly if you have already an __init__() method in the children class.

For example the following code will return an error: class parent: def init(self, a=1, b=0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

回答 7

也许__init__是子类需要重写的方法。有时,子类在添加特定于类的代码之前需要运行父级函数,而有时,它们需要在调用父级函数之前设置实例变量。由于Python不可能知道何时最适合调用这些函数,因此不应该猜测。

如果这些都不影响您,请考虑这__init__只是另一个功能。如果有问题的函数dostuff代替了,您是否仍然希望Python在父类中自动调用相应的函数?

Maybe __init__ is the method that the subclass needs to override. Sometimes subclasses need the parent’s function to run before they add class-specific code, and other times they need to set up instance variables before calling the parent’s function. Since there’s no way Python could possibly know when it would be most appropriate to call those functions, it shouldn’t guess.

If those don’t sway you, consider that __init__ is Just Another Function. If the function in question were dostuff instead, would you still want Python to automatically call the corresponding function in the parent class?


回答 8

我相信这里一个非常重要的考虑因素是,通过自动调用super.__init__(),您可以按设计禁止在何时调用该初始化方法以及使用哪些参数。避免自动调用它,并要求程序员明确地执行该调用,需要很大的灵活性。

毕竟,仅因为类B派生自类A并不意味着A.__init__()可以或应该使用与相同的参数进行调用B.__init__()。将调用明确化意味着程序员可以例如B.__init__()使用完全不同的参数进行定义,使用该数据进行一些计算,A.__init__()使用适合该方法的参数进行调用,然后进行一些后处理。如果A.__init__()B.__init__()B.__init__()执行之前或执行之后隐式调用,则很难获得这种灵活性。

i believe the one very important consideration here is that with an automatic call to super.__init__(), you proscribe, by design, when that initialization method is called, and with what arguments. eschewing automatically calling it, and requiring the programmer to explicitly do that call, entails a lot of flexibility.

after all, just because class B is derived from class A does not mean A.__init__() can or should be called with the same arguments as B.__init__(). making the call explicit means a programmer can have e.g. define B.__init__() with completely different parameters, do some computation with that data, call A.__init__() with arguments as appropriate for that method, and then do some postprocessing. this kind of flexibility would be awkward to attain if A.__init__() would be called from B.__init__() implicitly, either before B.__init__() executes or right after it.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。