詹姆斯·奈特的文章super() 被认为是有害的 https://fuhm.net/super-harmful/通过始终接受来提出解决方案*args
and **kwargs
在所有合作职能中。
然而这个解决方案不起作用有两个原因:
object.__init__
不接受争论
这是 python 2.6 / 3.x 引入的重大更改TypeError: object.__init__() takes no parameters https://menno.io/posts/object__init__takes_no_parameters/
using *args
实际上是适得其反的
解决方案 TL;DR
super()
用法必须一致:在类层次结构中, super 应该在任何地方使用,或者不在任何地方使用。是班级合同的一部分。如果一个类使用super()
所有课程MUST也使用super()
以同样的方式,否则我们可能会调用层次结构中的某些函数零次或多次
-
正确支持__init__
带有任何参数的函数,层次结构中的顶级类必须继承自自定义类,例如 SuperObject:
class SuperObject:
def __init__(self, **kwargs):
mro = type(self).__mro__
assert mro[-1] is object
if mro[-2] is not SuperObject:
raise TypeError(
'all top-level classes in this hierarchy must inherit from SuperObject',
'the last class in the MRO should be SuperObject',
f'mro={[cls.__name__ for cls in mro]}'
)
# super().__init__ is guaranteed to be object.__init__
init = super().__init__
init()
如果类层次结构中的重写函数可以采用不同的参数,则始终将收到的所有参数作为关键字参数传递给 super 函数,并且始终接受**kwargs
.
这是一个重写的例子
class A(SuperObject):
def __init__(self, **kwargs):
print("A")
super(A, self).__init__(**kwargs)
class B(SuperObject):
def __init__(self, **kwargs):
print("B")
super(B, self).__init__(**kwargs)
class C(A):
def __init__(self, age, **kwargs):
print("C",f"age={age}")
super(C, self).__init__(age=age, **kwargs)
class D(B):
def __init__(self, name, **kwargs):
print("D", f"name={name}")
super(D, self).__init__(name=name, **kwargs)
class E(C,D):
def __init__(self, name, age, *args, **kwargs):
print( "E", f"name={name}", f"age={age}")
super(E, self).__init__(name=name, age=age, *args, **kwargs)
e = E(name='python', age=28)
output:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
讨论
让我们更详细地看看这两个问题
object.__init__
不接受争论
考虑 James Knight 给出的原始解决方案:
一般规则是:始终将收到的所有参数传递给 super 函数,并且,如果类可以采用不同的参数,则始终接受*args
and **kwargs
.
class A:
def __init__(self, *args, **kwargs):
print("A")
super().__init__(*args, **kwargs)
class B(object):
def __init__(self, *args, **kwargs):
print("B")
super().__init__(*args, **kwargs)
class C(A):
def __init__(self, arg, *args, **kwargs):
print("C","arg=",arg)
super().__init__(arg, *args, **kwargs)
class D(B):
def __init__(self, arg, *args, **kwargs):
print("D", "arg=",arg)
super().__init__(arg, *args, **kwargs)
class E(C,D):
def __init__(self, arg, *args, **kwargs):
print( "E", "arg=",arg)
super().__init__(arg, *args, **kwargs)
print( "MRO:", [x.__name__ for x in E.__mro__])
E(10)
python 2.6 和 3.x 中的重大更改已更改object.__init__
签名,使其不再接受任意参数
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-9001c741f80d> in <module>
25
26 print( "MRO:", [x.__name__ for x in E.__mro__])
---> 27 E(10)
...
<ipython-input-2-9001c741f80d> in __init__(self, *args, **kwargs)
7 def __init__(self, *args, **kwargs):
8 print("B")
----> 9 super(B, self).__init__(*args, **kwargs)
10
11 class C(A):
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
处理这个难题的正确方法是让层次结构中的顶级类继承自自定义类,例如SuperObject
:
class SuperObject:
def __init__(self, *args, **kwargs):
mro = type(self).__mro__
assert mro[-1] is object
if mro[-2] is not SuperObject:
raise TypeError(
'all top-level classes in this hierarchy must inherit from SuperObject',
'the last class in the MRO should be SuperObject',
f'mro={[cls.__name__ for cls in mro]}'
)
# super().__init__ is guaranteed to be object.__init__
init = super().__init__
init()
因此重写示例如下应该可行
class A(SuperObject):
def __init__(self, *args, **kwargs):
print("A")
super(A, self).__init__(*args, **kwargs)
class B(SuperObject):
def __init__(self, *args, **kwargs):
print("B")
super(B, self).__init__(*args, **kwargs)
class C(A):
def __init__(self, arg, *args, **kwargs):
print("C","arg=",arg)
super(C, self).__init__(arg, *args, **kwargs)
class D(B):
def __init__(self, arg, *args, **kwargs):
print("D", "arg=",arg)
super(D, self).__init__(arg, *args, **kwargs)
class E(C,D):
def __init__(self, arg, *args, **kwargs):
print( "E", "arg=",arg)
super(E, self).__init__(arg, *args, **kwargs)
print( "MRO:", [x.__name__ for x in E.__mro__])
E(10)
output:
MRO: ['E', 'C', 'A', 'D', 'B', 'SuperObject', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
SuperObject
using *args
是适得其反的
让这个例子变得更复杂一些,有两个不同的参数:name
and age
class A(SuperObject):
def __init__(self, *args, **kwargs):
print("A")
super(A, self).__init__(*args, **kwargs)
class B(SuperObject):
def __init__(self, *args, **kwargs):
print("B")
super(B, self).__init__(*args, **kwargs)
class C(A):
def __init__(self, age, *args, **kwargs):
print("C",f"age={age}")
super(C, self).__init__(age, *args, **kwargs)
class D(B):
def __init__(self, name, *args, **kwargs):
print("D", f"name={name}")
super(D, self).__init__(name, *args, **kwargs)
class E(C,D):
def __init__(self, name, age, *args, **kwargs):
print( "E", f"name={name}", f"age={age}")
super(E, self).__init__(name, age, *args, **kwargs)
E('python', 28)
output:
E name=python age=28
C age=python
A
D name=python
B
SuperObject
正如你从线上看到的C age=python
位置参数变得混乱,我们传递了错误的东西。
我建议的解决方案是更加严格并避免*args
完全论证。反而:
如果类可以采用不同的参数,请始终将收到的所有参数传递给 super 函数作为关键字参数,并且,始终接受**kwargs
.
这是基于此更严格规则的解决方案。首先删除*args
from SuperObject
class SuperObject:
def __init__(self, **kwargs):
print('SuperObject')
mro = type(self).__mro__
assert mro[-1] is object
if mro[-2] is not SuperObject:
raise TypeError(
'all top-level classes in this hierarchy must inherit from SuperObject',
'the last class in the MRO should be SuperObject',
f'mro={[cls.__name__ for cls in mro]}'
)
# super().__init__ is guaranteed to be object.__init__
init = super().__init__
init()
现在删除*args
来自其余类,并仅按名称传递参数
class A(SuperObject):
def __init__(self, **kwargs):
print("A")
super(A, self).__init__(**kwargs)
class B(SuperObject):
def __init__(self, **kwargs):
print("B")
super(B, self).__init__(**kwargs)
class C(A):
def __init__(self, age, **kwargs):
print("C",f"age={age}")
super(C, self).__init__(age=age, **kwargs)
class D(B):
def __init__(self, name, **kwargs):
print("D", f"name={name}")
super(D, self).__init__(name=name, **kwargs)
class E(C,D):
def __init__(self, name, age, *args, **kwargs):
print( "E", f"name={name}", f"age={age}")
super(E, self).__init__(name=name, age=age, *args, **kwargs)
E(name='python', age=28)
output:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
哪个是对的