168

为什么 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'
4

10 回答 10

174

__init__Python和其他语言的构造函数之间的关键区别在于它__init__不是构造函数:它是一个初始化器(实际的构造函数(如果有的话,但是,请参见后面;-)是__new__并且工作方式完全不同)。在构造所有超类时(毫无疑问,在“继续向下构造之前”这样做)显然是说你正在构造一个子类的实例的一部分,这显然不是初始化的情况,因为有许多用例需要跳过、更改、控制超类的初始化——如果有的话,发生在子类初始化的“中间”,等等。

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

于 2010-09-23T22:07:53.707 回答
36

当人们鹦鹉学舌地模仿“Python 之禅”时,我感到有些尴尬,好像这是为任何事情辩护一样。这是一种设计理念;特定的设计决策总是可以用更具体的术语来解释——它们必须如此,否则“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__.

于 2010-09-23T22:08:51.020 回答
20

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

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

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

因此,Python 子类不调用其基类构造函数是完全合理的。如果它愿意,它可以只添加属性本身。BaseClass对于层次结构中的每个类,没有为给定数量的字段保留空间,并且方法中的代码添加的属性和方法中的代码添加的属性之间没有区别SubClass

如果像常见的那样,SubClass实际上确实希望BaseClass在继续进行自己的定制之前设置所有的不变量,那么是的,您可以调用BaseClass.__init__()(或使用super,但这很复杂并且有时有自己的问题)。但你不必这样做。你可以在之前、之后或使用不同的论点来做。地狱,如果你愿意,你可以BaseClass.__init__完全从另一个方法调用__init__; 也许你有一些奇怪的懒惰初始化的事情。

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

于 2011-10-24T05:20:01.453 回答
13

__init__()为避免混淆,如果 child_class 没有类,您可以调用 base_class 方法是很有用的__init__()

例子:

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

事实上,python中的MRO在__init__()子类中找不到时会在父类中查找。如果子类中已有__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.
于 2017-10-25T23:56:14.907 回答
10

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

我认为这最终是一个好处——你能背诵 Java 关于调用超类的构造函数的所有规则吗?

于 2010-09-23T21:59:08.550 回答
8

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

如果自动调用构造函数,则您需要另一个至少相同长度的页面来解释发生的顺序。那将是地狱...

于 2010-09-23T22:12:23.843 回答
7

通常子类有额外的参数不能传递给超类。

于 2010-09-23T22:00:32.753 回答
3

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

如果这些不影响您,请考虑这__init__只是另一个功能。如果有问题的函数是dostuff相反的,你还希望 Python 自动调用父类中的相应函数吗?

于 2010-09-23T22:13:15.167 回答
2

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

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

于 2010-09-24T00:17:26.303 回答
0

正如 Sergey Orshanskiy 在评论中指出的那样,编写一个装饰器来继承该__init__方法也很方便。

您可以编写一个装饰器来继承该__init__方法,甚至可以自动搜索子类并装饰它们。– 谢尔盖 Orshanskiy 2015 年 6 月 9 日 23:17

第 1/3 部分:实施

注意:实际上,这仅在您想同时调用基类和派生类时才有用,__init__因为__init__是自动继承的。请参阅此问题的先前答案。

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        super(type(self), self).__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child(42)

输出:

Base: 42

第 2/3 部分:警告

base警告:如果它本身被调用,这不起作用super(type(self), self)

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        '''Warning: recursive calls.'''
        super(type(self), self).__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
class child2(child):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child2(42)

RecursionError:调用 Python 对象时超出了最大递归深度。

第 3/3 部分:为什么不直接使用 plain super()

但是为什么不直接使用安全平原super()呢?因为它不起作用,因为新的重新绑定__init__来自类外,并且super(type(self), self)是必需的。

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child(42)

错误:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-9-6f580b3839cd> in <module>
     13         pass
     14 
---> 15 child(42)

<ipython-input-9-6f580b3839cd> in wrapper(self, *args, **kwargs)
      1 def default_init(func):
      2     def wrapper(self, *args, **kwargs) -> None:
----> 3         super().__init__(*args, **kwargs)
      4     return wrapper
      5 

RuntimeError: super(): __class__ cell not found
于 2021-07-27T17:49:12.353 回答