既然这个问题是关于继承和super
,那么让我们从编写一个类开始。下面是一个简单的日常类,代表一个人:
class Person:
def __init__(self, name):
super().__init__()
self.name = name
就像每一个好的类一样,它在初始化自身之前调用其父构造函数。这个类的工作做得很好;它可以毫无问题地使用:
^{pr2}$但是当我试图创建一个同时继承Person
和另一个类的类时,事情突然出了问题:
class Horse:
def __init__(self, fur_color):
super().__init__()
self.fur_color = fur_color
class Centaur(Person, Horse):
def __init__(self, name, fur_color):
# ??? now what?
super().__init__(name) # throws TypeError: __init__() missing 1 required positional argument: 'fur_color'
Person.__init__(self, name) # throws the same error
由于diamond继承(顶部是object
类),因此无法正确初始化Centaur
实例。Person
中的super().__init__()
最后调用了Horse.__init__
,这将引发异常,因为缺少fur_color
参数。在
但是如果Person
和{super().__init__()
,这个问题就不会存在。在
这就产生了一个问题:直接从object
继承的类是否应该调用super().__init__()
?如果是,如何正确初始化Centaur
?
免责声明:我知道what ^{
为什么我要特别询问object
即使钻石继承也可以与其他类一起发生?这是因为object
在python的类型层次结构中有一个特殊的位置,无论您是否喜欢,它都位于MRO的顶部。通常只有当为了实现与该类相关的特定目标而故意从某个基类继承时,才会发生菱形继承。在这种情况下,钻石的继承是意料之中的。但是如果菱形顶部的类是object
,那么两个父类很可能是完全不相关的,并且有两个完全不同的接口,因此出错的可能性更大。在
如果}从来没有被设计成用作同一类的基类,那么{}可能不应该存在。正确地设计多重继承是非常困难的,远不止调用
Person
和{super
。即使是单一的继承也是相当棘手的。在如果}(可能还有它们周围的类)需要重新设计。从这里开始:
Person
和Horse
是支持创建类的,那么Person
和{你会注意到一些变化。让我们看看名单。在
第一,{{CD10}}签名在中间有一个^ {CD11}}。}现在都是关键字。当多重继承图中的不同类采用不同的参数时,几乎不可能让位置参数安全工作,因此为了安全起见,我们需要按关键字的参数。(如果多个类需要使用相同的构造函数参数,情况会有所不同。)
*
标记仅关键字参数的开始:name
和{第二,
__init__
签名现在全部采用**kwargs
。这使得Person.__init__
接受它不理解的关键字参数,比如fur_color
,并将它们传递给下一行,直到它们到达任何一个能理解它们的类。当参数到达object.__init__
时,object.__init__
应该接收到空的kwargs
。在第三,},它不需要
Centaur
不再有自己的__init__
。重新设计了Person
和{__init__
。从Person
继承的__init__
将正确处理Centaur
的MRO,将fur_color
传递给Horse.__init__
。在使用
super
调用超类方法时,通常需要当前类和当前实例作为参数:现在,问题出在Python处理walking the superclasses的方式上。如果
^{pr2}$__init__
方法没有匹配的签名,那么调用可能会导致问题。从链接的示例:输出为:
这显示了调用构造函数的顺序。还要注意,子类构造函数都有兼容的签名,因为它们是在心里彼此编写的。在
你似乎在为这个问题寻找一个简单的“是或否”答案,但不幸的是,答案是“视情况而定”。此外,在决定是否应该调用
super().__init__()
时,类是否直接从object
继承有些无关紧要。不变的是,如果调用了object.__init__
,那么应该不带参数地调用它,因为object.__init__
不接受参数。在实际上,在协作继承的情况下,这意味着您必须确保在调用}上下文已从可变映射^{中弹出。在
object.__init__
之前,所有参数都被使用。它并不意味着您应该避免调用object.__init__
。Here是在调用super
之前使用参数的一个例子,response
和{我在前面提到过,一个类是否直接从
object
继承是一个红鲱鱼1。但是我还没有提到什么应该激励这个设计决策:如果你想让MRO继续搜索其他初始化器[read:otheranymethod
],那么应该调用super init[read:superanymethod
]。如果您希望在此处停止MRO搜索,则不应调用super。在如果
object.__init__
不起任何作用,为什么它会存在呢?因为它做了一些事情:确保调用它时没有参数。参数的存在很可能表示bug2。object
还可以用来停止超级调用链-某人不必调用super,否则我们将无限递归。您可以通过不调用super直接自己停止它。如果不这样做,object
将作为最后一个链接,并为您停止链。在类MRO在编译时确定,通常是在定义类/导入模块时。但是,请注意,
super
的使用涉及到很多运行时分支的机会。你必须考虑:super
方法时使用哪些参数(例如,您希望沿着MRO转发哪些参数)super
本身的参数(如果有)(下面描述了一个高级用例)__init__
,但是不要忘记super也可以与任何其他方法一起使用在极少数情况下,您可能会有条件地调用} 。老实说,
super
调用。您可以检查您的super()
实例是否具有this或that属性,并根据结果建立一些逻辑。或者,您可以调用super(OtherClass, self)
显式地“跳过”一个链接,并手动遍历该部分的MRO。是的,如果默认行为不是你想要的,你可以劫持MRO!所有这些邪恶思想的共同点是对C3 linearization算法的理解,Python如何生成MRO,以及super本身如何使用MRO。Python的实现或多或少是从another programming language中提取出来的,其中super被命名为^{super
在Python中是一个非常糟糕的名字,因为它在初学者中造成了一个常见的误解,即您总是调用“最多”一个父类,我希望他们选择了一个更好的名称。在在定义继承层次结构时,解释器无法知道您是否希望重用某些其他类的现有功能,还是将其替换为替代实现,或者其他什么。任何一个决定都可能是一个有效和实用的设计。如果有一个关于何时和如何
super
的硬性规定当被调用时,它不会由程序员来选择——语言将从你的手中夺走决定权,只是自动地做正确的事情。我希望这能充分解释在__init__
中调用super不是一个简单的yes/no问题。在(问题
Foo
,SuperFoo
等的来源)为了回答这一部分,我假设MCVE中显示的
__init__
方法实际上需要进行一些初始化(也许您可以在问题的MCVE代码中添加占位符注释)。根本不要定义__init__
如果你所做的只是用相同的参数调用super,那就没有意义了。不要定义一个__init__
,它只是pass
,除非您有意在那里停止MRO遍历(在这种情况下,肯定需要注释!)。在首先,在我们讨论}看起来是一个不完整或糟糕的设计。如何将
SuperFoo
之前,让我说{foo
参数传递给Foo
的init?3
的foo
初始值是硬编码的。硬编码(或自动确定)foo的init值可能是可以的,但是you should probably be doing composition not inheritance。在至于}。
SuperFoo
,它继承了SuperCls
和{SuperCls
看起来是为了继承,Foo
不是。这意味着您可能有一些工作要做,正如super harmful所指出的那样。一种前进的方法,as discussed in Raymond's blog,是编写适配器。在注意,}是一个}进行合作:
^{pr2}$FooAdapter
有一个Foo
,而不是{Foo
。这不是唯一可能的设计选择。但是,如果您像class FooParent(Foo)
那样继承,那么您就意味着FooParent
是一个Foo
,并且可以用于Foo
的任何地方,使用组合通常更容易避免违反LSP。SuperCls
还应通过允许{也许{}也超出了你的控制范围,你也必须适应它,所以就这样吧。关键是,这是一种重用代码的方法,通过调整接口使签名匹配。假设每个人都很好地合作并消费他们所需要的,最终
super().__init__(**kwargs)
将代理object.__init__(**{})
。在不,因为YAGNI。99%的类在有用之前,是否需要立即支持100%的常规依赖注入?如果它们不坏的话,是不是坏了?作为一个例子,考虑集合文档中给出的^{} 配方。^{} 接受},但是{a13}。如果你想使用这些参数中的一个,运气不好,你必须重写} 根本没有协同定义,实际上,一些parent calls are hardcoded到{}-并且任何下一行的
*args
和{__init__
并截获它们。^{__init__
都不会被调用,因此任何MRO遍历都将在其轨道上停止。如果您意外地将其定义为OrderedCounter(OrderedDict, Counter)
而不是OrderedCounter(Counter, OrderedDict)
,元类基仍然能够创建一致的MRO,但是该类根本不能作为有序计数器工作。在尽管有这些缺点,但是
OrderedCounter
配方还是像宣传的那样工作,因为MRO是为预期的用例设计的。因此,为了实现依赖注入,您甚至不需要100%正确地进行协作继承。这个故事的寓意是完美是进步的敌人。如果你想把MyWhateverClass
塞进你能想象得到的任何疯狂的继承树中,那就继续吧,但这是由你自己来编写必要的脚手架来实现的。与往常一样,Python不会阻止您以任何足够好的方法来实现它。在1无论是否在类声明中编写,都始终从object继承。为了与2.7运行时交叉兼容,许多开源代码库都会显式地从object继承。
2这一点将与sub一起进行更详细的解释CPython源here中}之间的关系。
__new__
和{相关问题 更多 >
编程相关推荐