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

时间:2010-09-23 21:56:13

标签: python inheritance subclass delegation superclass

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

10 个答案:

答案 0 :(得分:156)

Python的__init__和其他语言构造函数之间的关键区别在于__init__ 不是构造函数:它是初始值设定项(实际的构造函数(如果有的话,但是,请参阅后面的内容;​​-)是__new__,并且完全不同的工作方式)。虽然构建所有超类(并且毫无疑问,在“继续向下构建之前”这样做)显然是说你构建子类的实例,这显然是不是初始化的情况,因为有很多用例需要跳过,更改,控制超级类的初始化,如果有的话,在子类初始化的“中间”,等等。

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

答案 1 :(得分:36)

当人们嘲笑“禅宗蟒蛇”时,我有点尴尬,好像这是任何事情的理由。这是一种设计理念;特定的设计决策可以始终以更具体的术语解释 - 它们必须是,或者“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__

答案 2 :(得分:16)

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

如果您的成员BaseClass包含成员field1,并且您创建了一个新成员SubClass,其中添加了成员​​field2,那么{{1}的实例}}包含SubClassfield1的空格。您需要field2的构造函数来填充BaseClass,除非您需要所有继承类在其自己的构造函数中重复field1的初始化。如果BaseClass是私有的,那么继承类就不能初始化field1

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

因此,对于不调用其基类构造函数的Python子类来说,这是完全合理的。如果需要,它可以自己添加属性。对于层次结构中的每个类,没有为给定数量的字段保留空间,并且来自field1方法的代码添加的属性与来自BaseClass方法的代码添加的属性之间没有区别。

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

Python通过简单易用来实现这种灵活性。您可以通过编写BaseClass.__init__方法来初始化对象,该方法在__init__上设置属性。而已。它的行为与方法完全相同,因为它只是一种方法。关于必须首先完成的事情,或者如果你不做其他事情会自动发生的事情,没有其他奇怪和不直观的规则。它需要服务的唯一目的是成为在对象初始化期间执行以设置初始属性值的钩子,并且它就是这样做的。如果您希望它执行其他操作,请在代码中明确写入。

答案 3 :(得分:10)

“明确比隐含更好。”同样的推理表明我们应该明确地写出“自我”。

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

答案 4 :(得分:8)

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

答案 5 :(得分:7)

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

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

答案 6 :(得分:4)

为避免混淆,如果child_class没有__init__()类,则知道可以调用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

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

例如,以下代码将返回错误:         班级家长:           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 :(得分:3)

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

如果那些不影响你,请考虑__init__只是另一个功能。如果相关函数改为dostuff,您是否仍希望Python自动调用父类中的相应函数?

答案 8 :(得分:2)

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

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

答案 9 :(得分:0)

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

<块引用>

你可以写一个装饰器来继承__init__方法,甚至可能会自动搜索子类并装饰它们。 – Sergey 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 部分:为什么不直接使用普通的 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