【问题标题】:Why aren't superclass __init__ methods automatically invoked?为什么不自动调用超类 __init__ 方法?
【发布时间】:2011-04-16 12:27:06
【问题描述】:

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

【问题讨论】:

  • 您可以编写一个装饰器来继承__init__ 方法,甚至可以自动搜索子类并进行装饰。
  • @osa,听起来真是个好主意。你想多描述一下装饰器部分吗?
  • @osa 是的,请描述更多!
  • @Diansheng 我想这只是给super()的一个电话。
  • @Diansheng 我会添加一个答案。

标签: python inheritance subclass delegation superclass


【解决方案1】:

正如 Sergey Orshanskiy 在 cmets 中指出的那样,写一个装饰器来继承 __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 部分:为什么不直接使用普通 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

【讨论】:

    【解决方案2】:

    为避免混淆,如果 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
    

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

    例如以下代码将返回错误: 班级家长: def init(self, a=1, b=0): 自我.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.
    

    【讨论】:

    • 关键是 [init] 只会被调用一次,在实例实例化过程中继承树搜索中最低的一个。如果要调用超类 [init],则必须使用 super().__init__() 递归地显式调用它们。如果树路径上没有定义 [init],则不会调用任何内容。它只是一个在构造时自动调用一次的普通函数。您可以稍后使用 child.__init__(p, 4, 5) 之类的内容调用它。
    • 这是否记录在 Python 文档中?我找到object.__init__() 并阅读“如果基类具有__init__() 方法,则派生类的__init__() 方法(如果有)必须显式调用它以确保正确初始化实例的基类部分”。但这并没有指定派生类缺少 __init__() 时的行为。
    【解决方案3】:

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

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

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

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

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

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

    【讨论】:

    • 至于C++,与“内存布局”无关。与 Python 提供的相同初始化模型可以在 C++ 中实现。构造/销毁是它们在 C++ 中的方式的唯一原因是因为设计决定为资源管理 (RAII) 提供可靠且性能良好的设施,这也可以自动生成(意味着更少的代码和人为错误)由编译器,因为它们的规则(它们的调用顺序)是严格定义的。不确定,但 Java 很可能只是遵循这种方法成为另一种类似 POLA C 的语言。
    • 我认为这是一个 F-ed up 的选择,但 +1 以一致地解释它。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 这是正确的答案。 Python 需要定义子类和超类之间参数传递的语义。太糟糕了,这个答案没有被赞成。也许如果它通过一些例子显示了问题?
        【解决方案7】:

        当人们鹦鹉学舌地模仿“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__

        【讨论】:

        • 这个例子有什么特别之处吗?静态语言也可以做到这一点
        • 然后呢?它如何防止默认(无参数)超类构造函数被隐式调用?这就是其他语言(C++、Java 等)所做的。
        【解决方案8】:

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

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

        【讨论】:

        • 赞成,因为对我来说,真正的好处是你提到的在子类初始化的任何时候调用超类的 _init()_ 的能力(或根本不) .
        • -1 表示“__init__ 不是构造函数...实际的构造函数...是__new__”。正如您自己注意到的,__new__ 的行为与其他语言的构造函数完全不同。 __init__ 实际上非常相似(在创建新对象期间调用它,在分配该对象之后,在新对象上设置成员变量),并且几乎总是在实现在其他语言中您将放入构造函数中的功能。所以就叫它构造函数吧!
        • 我也认为这是一个相当荒谬的说法:“constructing all superclasses ...显然是说你在constructing一个子类的例如,initializing 显然不是这种情况。构造/初始化这两个词中没有任何内容可以使任何人“显而易见”。并且__new__ 也不会自动调用超类__new__。因此,您声称关键区别在于 construction 必然涉及超类的构造,而 initialization 与您声称 __new__ 是构造函数并不矛盾。跨度>
        • 事实上,来自__init__docs.python.org/reference/datamodel.html#basic-customization 的 python 文档:“作为对 constructors 的特殊约束,可能不会返回任何值;这样做会导致TypeError 要在运行时引发”(强调我的)。所以这里是官方的,__init__ 是一个构造函数。
        • 在 Python/Java 术语中,__init__ 被称为构造函数。此构造函数是在对象已完全构造并初始化为默认状态(包括其最终运行时类型)后调用的初始化函数。它不等同于 C++ 构造函数,后者在具有未定义状态的静态类型分配对象上调用。这些也和__new__不同,所以我们确实至少有四种不同的分配/构造/初始化函数。语言使用混合术语,重要的是行为而不是术语。
        【解决方案9】:

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

        【讨论】:

        • 然后呢?它如何防止默认(无参数)超类构造函数被隐式调用?这就是其他语言(C++、Java 等)所做的。
        【解决方案10】:

        “显式优于隐式。”同样的道理表明我们应该明确地写成'self'。

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

        【讨论】:

        • 我在很大程度上同意你的看法,但 Java 的规则实际上非常简单:调用无参数构造函数,除非你特别要求不同的构造函数。
        • @Laurence - 当父类没有定义无参数构造函数时会发生什么?当无参数构造函数被保护或私有时会发生什么?
        • 如果你试图显式调用它会发生完全相同的事情。
        猜你喜欢
        • 1970-01-01
        • 2011-11-06
        • 1970-01-01
        • 2019-08-04
        • 1970-01-01
        • 1970-01-01
        • 2014-08-15
        • 2012-07-23
        相关资源
        最近更新 更多