由于这个问题是关于继承和 Dim str As [String] = ""
For i As Integer = 0 To CheckBoxList1.Items.Count - 1
If CheckBoxList1.Items(i).Selected Then
If str = "" Then
str = "'" + CheckBoxList1.Items(i).Value + "'"
Else
str += "," + "'" + CheckBoxList1.Items(i).Value + "'"
End If
End If
Next
'display in a textbox or lable with ID txtmsg
txtmsg.Text = str
的,所以让我们从编写一个类开始。这是一个代表人的简单日常课程:
super
就像每个好的类一样,它会在初始化自身之前调用其父构造函数。这个班的工作做得很好。它可以毫无问题地使用:
class Person:
def __init__(self, name):
super().__init__()
self.name = name
但是当我尝试创建一个同时继承>>> Person('Tom')
<__main__.Person object at 0x7f34eb54bf60>
和另一个类的类时,事情突然出错了:
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
类,因此无法正确初始化object
实例。 Centaur
中的super().__init__()
最终调用Person
,由于缺少Horse.__init__
参数而引发异常。
但是,如果fur_color
和Person
没有呼叫Horse
,就不会存在此问题。
这引发了一个问题:直接从super().__init__()
继承的类应该调用object
吗?如果是,您将如何正确初始化super().__init__()
?
免责声明:我知道what super
does,MRO的工作方式和how super
interacts with multiple inheritance。我了解导致此错误的原因。我只是不知道避免错误的正确方法是什么。
为什么即使其他类也可能发生钻石继承,我仍要特别询问Centaur
吗?这是因为object
在python的类型层次结构中具有特殊的位置-无论您是否喜欢,它都位于MRO的顶部。通常,只有当您故意从某个基类中继承以实现与该类相关的特定目标时,钻石继承才会发生。在这种情况下,钻石继承是可以预期的。但是,如果位于最上方的类是object
,则可能是您的两个父类是完全不相关的,并且具有两个完全不同的接口,因此出现问题的可能性更大。
答案 0 :(得分:7)
如果从未将Person
和Horse
用作同一类的基类,那么Centaur
可能不应该存在。正确设计多重继承非常困难,远不止是调用super
。即使是单一继承也非常棘手。
如果假设Person
和Horse
支持创建Centaur
之类的类,则Person
和Horse
(以及可能需要围绕它们的类)进行一些重新设计。这是一个开始:
class Person:
def __init__(self, *, name, **kwargs):
super().__init__(**kwargs)
self.name = name
class Horse:
def __init__(self, *, fur_color, **kwargs):
super().__init__(**kwargs)
self.fur_color = fur_color
class Centaur(Person, Horse):
pass
stevehorse = Centaur(name="Steve", fur_color="brown")
您会注意到一些变化。让我们进入列表。
首先,__init__
个签名现在中间有一个*
。 *
标志着仅关键字参数的开始:name
和fur_color
现在仅关键字。当多重继承图中的不同类采用不同的参数时,几乎不可能使位置参数安全地工作,因此,为了安全起见,我们要求使用关键字作为参数。 (如果多个类需要使用相同的构造函数参数,则情况将有所不同。)
第二,__init__
个签名现在全部取**kwargs
。这样,Person.__init__
就可以接受fur_color
之类的无法理解的关键字参数,并将其传递给行,直到它们到达任何可以理解的类为止。到参数达到object.__init__
时,object.__init__
应该收到空的kwargs
。
第三,Centaur
不再拥有自己的__init__
。经过重新设计的Person
和Horse
,它不需要__init__
。从__init__
继承来的Person
对Centaur
的MRO会做正确的事情,将fur_color
传递给Horse.__init__
。
答案 1 :(得分:6)
直接从
object
继承的类应该调用super().__init__()
吗?
您似乎在搜索该问题的简单“是或否”答案,但是不幸的是答案是“取决于”。此外,在决定是否应调用super().__init__()
时,类是否直接继承自object
有点无关紧要。不变的是,如果调用object.__init__
,则应在不带参数的情况下调用它-因为object.__init__
不接受参数。
实际上,在合作继承的情况下,这意味着您必须确保在调用object.__init__
之前,所有参数都已消耗 。这不是不是的意思,您应该尝试避免调用object.__init__
。 Here是在调用super
之前消耗args的示例,response
和request
上下文已从可变映射kwargs
中弹出。
我之前提到过,一个类是否直接从object
继承是一个红色鲱鱼 1 。但是我还没有提到什么可以激发这个设计决定:如果您希望继续在MRO中搜索其他初始化程序,则应调用super init [read:super anymethod
] [read:other {{1} } s]。如果要指示应该在此处停止MRO搜索,则不应调用super。
为什么anymethod
根本不存在,为什么会存在?因为它确实做了一些事情:确保了它不带参数地调用。参数的存在可能表示错误 2 。 object.__init__
的作用还在于停止超级调用链-某人不必调用超级,否则我们将无限递归。您可以通过不调用super来明确地自己停止它。如果没有,object
将作为最终链接并为您停止链接。
MRO类是在编译时确定的,通常是在定义类/导入模块时确定。但是,请注意,object
的使用涉及 runtime 分支的许多机会。您必须考虑:
super
的方法调用哪个参数(即要沿MRO转发的参数)super
(如果有的话,下面有一个高级用例)super
,但不要忘记,super也可以与其他任何方法一起使用在极少数情况下,您可能有条件地调用__init__
调用。您可以检查您的super
实例是否具有此属性或该属性,并根据结果确定一些逻辑。或者,您可以调用super()
来显式“跳过”链接并手动遍历此部分的MRO。是的,如果默认行为不是您想要的,则可以劫持MRO!所有这些恶魔般的想法的共同点是对C3 linearization算法的理解,Python如何进行MRO以及super本身如何使用MRO。 Python的实现或多或少地从another programming language(其中super被命名为next-method
)中解放出来。坦白地说,super(OtherClass, self)
在Python中是一个超级坏名字,因为它引起初学者之间的普遍误解,即您总是向一个父类调用“ up”,我希望他们选择了一个更好的名字。
在定义继承层次结构时,解释器无法知道您是否要重用其他一些现有功能的类,还是要用替代实现将其替换? 。任何一个决定都可能是有效且实用的设计。如果在何时何地如何调用super
上有一个严格的规定,那么程序员就不必选择它了-该语言将自动做出决定,并自动执行正确的操作。我希望能充分说明在super
中调用super并不是一个简单的是/否问题。
如果是,您将如何正确初始化
__init__
?
(问题的this revision中SuperFoo
,Foo
等的来源)
为了回答这一部分,我将假定MCVE中显示的SuperFoo
方法实际上需要进行一些初始化(也许您可以在问题的MCVE代码中添加占位符注释)。如果您唯一要做的就是使用相同的参数调用super,则根本不要定义__init__
,这毫无意义。不要定义仅__init__
的{{1}},除非您有意停止在那里的MRO遍历(在这种情况下,一定要发表评论!)。
首先,在我们讨论__init__
之前,让我说pass
看起来像是不完整的或不良的设计。您如何将SuperFoo
参数传递给NoSuperFoo
的init? foo
的{{1}}初始值已硬编码。硬编码(或以其他方式自动确定)foo的初始值可能是可以的,但是可以you should probably be doing composition not inheritance。
对于Foo
,它继承了foo
和3
。 SuperFoo
看起来是为继承而设计的,SuperCls
不是。正如super harmful中指出的,这意味着您可能需要做一些工作。一种前进的方式,as discussed in Raymond's blog是编写适配器。
Foo
请注意,SuperCls
有一个 Foo
,而不是class FooAdapter:
def __init__(self, **kwargs):
foo_arg = kwargs.pop('foo')
# can also use kwargs['foo'] if you want to leave the responsibility to remove 'foo' to someone else
# can also use kwargs.pop('foo', 'foo-default') if you want to make this an optional argument
# can also use kwargs.get('foo', 'foo-default') if you want both of the above
self._the_foo_instance = Foo(foo_arg)
super().__init__(**kwargs)
# add any methods, wrappers, or attribute access you need
@property
def foo():
# or however you choose to expose Foo functionality via the adapter
return self._the_foo_instance.foo
是一个 FooAdapter
。这不是唯一可能的设计选择。但是,如果您像Foo
一样继承,则意味着FooAdapter
是 Foo
,并且可以在{{ 1}}否则会更容易-通过使用组合来避免违反LSP常常更容易。 class FooParent(Foo)
还应通过允许FooParent
进行合作:
Foo
也许Foo
也超出了您的控制范围,您也必须对其进行调整,就这样吧。关键是,这是通过调整接口以使签名匹配来重用代码的方法。假设每个人都很好地合作并消费了他们所需的东西,那么SuperCls
最终将代理**kwargs
。
由于我已经看到99%的类在其构造函数中未使用
class SuperCls: def __init__(self, **kwargs): # some other init code here super().__init__(**kwargs)
,这是否意味着99%的python类实现不正确?
否,因为YAGNI。在99%的类有用之前,是否需要立即支持所有风吹草动的100%一般依赖注入?如果没有,它们会破裂吗?例如,请考虑集合文档中提供的OrderedCounter
配方。 Counter.__init__
接受SuperCls
和super().__init__(**kwargs)
,但接受doesn't proxy them in the super init call。如果您想使用其中一个参数,祝您好运,那么必须重写object.__init__(**{})
并截取它们。 OrderedDict
根本没有被合作定义,实际上,某些parent calls are hardcoded到**kwargs
-并且未调用下一行中的任何内容的*args
,因此任何MRO遍历都会停在那儿。如果您不小心将其定义为**kwargs
而不是__init__
,则元类基础仍然可以创建一致的MRO,但该类根本无法用作有序计数器。
尽管存在所有这些缺点,但dict
配方仍然可以如所宣传的那样工作,因为MRO是按照针对预期用例设计的。因此,您甚至不需要为实现依赖项注入而正确地进行100%合作继承。这个故事的寓意是,完美是进步的敌人(或practicality beats purity)。如果您想将__init__
塞进任何疯狂的继承树中,就可以做梦了,那就继续吧,但是您需要编写必要的脚手架来允许这样做。像往常一样,Python不会阻止您以足够好用的hacky方式来实现它。
1 无论您是否在类声明中编写对象,都始终从对象继承。无论如何,许多开源代码库都会从对象显式继承,以便与2.7运行时交叉兼容。
2 在CPython来源here中将更详细地说明这一点以及OrderedCounter(OrderedDict, Counter)
和OrderedCounter(Counter, OrderedDict)
之间的微妙关系。 / sub>
答案 2 :(得分:-1)
使用super
调用超类方法时,通常需要使用当前类和当前实例作为参数:
super(Centaur, self).__init__(...)
现在,问题出在Python处理walking the superclasses的方式上。如果__init__
方法没有匹配的签名,则调用可能会引起问题。从链接的示例中:
class First(object):
def __init__(self):
print "first prologue"
super(First, self).__init__()
print "first epilogue"
class Second(First):
def __init__(self):
print "second prologue"
super(Second, self).__init__()
print "second epilogue"
class Third(First):
def __init__(self):
print "third prologue"
super(Third, self).__init__()
print "third epilogue"
class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print "that's it"
Fourth()
输出为:
$ python2 super.py
second prologue
third prologue
first prologue
first epilogue
third epilogue
second epilogue
that's it
这显示了构造函数的调用顺序。还要注意,子类构造函数都具有兼容的签名,因为它们是相互记住的。