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

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

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


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

“显性比隐性好。”这是同样的推理,表明我们应该明确地写’自我’。

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

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

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

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

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

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

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

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

如果这些都不能动摇你,那么考虑一下 __init__只是另一个函数。如果所讨论的函数是 dostuff,您是否仍然希望 Python 自动调用父类中的相应函数?

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

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

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

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

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

因此,Python 子类不调用它的基类构造函数是非常有意义的。如果需要的话,它可以自己添加属性。对于层次结构中的每个类,没有为给定数量的字段保留空间,由 BaseClass方法的代码添加的属性和由 SubClass方法的代码添加的属性之间也没有区别。

如果像通常一样,SubClass实际上希望在进行自定义之前设置所有 BaseClass的不变量,那么是的,你可以直接调用 BaseClass.__init__()(或者使用 super,但这很复杂,有时也有自己的问题)。但你不必这么做。你可以在之前,之后,或者用不同的论点来做。见鬼,如果你想,你可以完全从另一个方法调用 abc4而不是 __init__,也许你有一些奇怪的惰性初始模式。

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

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

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

例如,下面的代码将返回一个错误: 班级家长: Def Init(self,a = 1,b = 0) : 自我 自我

    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.

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

您可以编写一个装饰器来继承 __init__方法,甚至可以自动搜索子类并装饰它们。- Sergey Orshansky Jun 9’15 at 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

背景-我们可以自动初始化父类和子类!

这里有很多答案,并说“这不是 Python 的方式,从子类使用 super().__init__()”。问题不是要求使用 蟒蛇的方式,而是将其他语言的预期行为与 python 的明显不同的行为进行比较。

MRO文档很漂亮,也很丰富多彩,但它实际上是一个 TLDR 情况,并且仍然没有完全回答这个问题,在这些类型的比较中经常出现这种情况——“用 Python 的方式做,因为”.

继承的对象可以被子类中的后续声明重载,基于@keyvanrm (https://stackoverflow.com/a/46943772/1112676)答案的模式构建解决了这样一种情况: 我希望 自动启动父类作为调用类的一部分,而不需要在每个子类中显式调用 super().__init__()

在我的案例中,一个新的团队成员可能会被要求使用一个样板模板(用于在不触及核心应用程序源代码的情况下对我们的应用程序进行扩展) ,我们希望作为 光秃秃的,很容易采用这个模板,而不需要他们知道或理解底层机制——只需要知道和使用应用程序的基础界面提供的内容,这些内容已经有很好的文档记录。

对于那些会说“显性比隐性好”的人然而,我一般同意,当从许多其他流行语言继承自动初始化是预期的行为,它是非常有用的,如果它可以利用项目,其中一些工作在核心应用程序和其他工作在扩展它。

这种技术甚至可以为 init 传递 args/关键字 args,这意味着几乎任何对象都可以推送到父类,并由父类或其亲属使用。

例如:


class Parent:
def __init__(self, *args, **kwargs):
self.somevar = "test"
self.anothervar = "anothertest"


#important part, call the init surrogate pass through args:
self._init(*args, **kwargs)


#important part, a placeholder init surrogate:
def _init(self, *args, **kwargs):
print("Parent class _init; ", self, args, kwargs)


def some_base_method(self):
print("some base method in Parent")
self.a_new_dict={}




class Child1(Parent):
# when omitted, the parent class's __init__() is run
#def __init__(self):
#    pass


#overloading the parent class's  _init() surrogate
def _init(self, *args, **kwargs):
print(f"Child1 class _init() overload; ",self, args, kwargs)


self.a_var_set_from_child = "This is a new var!"




class Child2(Parent):
def __init__(self, onevar, twovar, akeyword):
print(f"Child2 class __init__() overload; ", self)


#call some_base_method from parent
self.some_base_method()


#the parent's base method set a_new_dict
print(self.a_new_dict)




class Child3(Parent):
pass




print("\nRunning Parent()")
Parent()
Parent("a string", "something else", akeyword="a kwarg")


print("\nRunning Child1(), keep Parent.__init__(), overload surrogate Parent._init()")
Child1()
Child1("a string", "something else", akeyword="a kwarg")


print("\nRunning Child2(), overload Parent.__init__()")
#Child2() # __init__() requires arguments
Child2("a string", "something else", akeyword="a kwarg")


print("\nRunning Child3(), empty class, inherits everything")
Child3().some_base_method()


产出:

Running Parent()
Parent class _init;  <__main__.Parent object at 0x7f84a721fdc0> () {}
Parent class _init;  <__main__.Parent object at 0x7f84a721fdc0> ('a string', 'something else') {'akeyword': 'a kwarg'}


Running Child1(), keep Parent.__init__(), overload surrogate Parent._init()
Child1 class _init() overload;  <__main__.Child1 object at 0x7f84a721fdc0> () {}
Child1 class _init() overload;  <__main__.Child1 object at 0x7f84a721fdc0> ('a string', 'something else') {'akeyword': 'a kwarg'}


Running Child2(), overload Parent.__init__()
Child2 class __init__() overload;  <__main__.Child2 object at 0x7f84a721fdc0>
some base method in Parent
{}


Running Child3(), empty class, inherits everything, access things set by other children
Parent class _init;  <__main__.Child3 object at 0x7f84a721fdc0> () {}
some base method in Parent

可以看到,重载定义取代了在 Parent 类中声明的定义,但仍然可以通过 Parent 类调用因此允许模拟经典的隐式继承初始化行为 Parent 和 Child 类都进行初始化,而无需从 Child 类显式调用 Parent 的 Init()。

就我个人而言,我称代理 _ init ()方法为 main(),因为它对我来说在 C + + 和 Python 之间切换是有意义的,例如,因为它是为 Parent 的任何子类(即最后声明的 main()定义)自动运行的函数。