James Knight 的文章 super() considered harmful 提出了一个解决方案,即在所有协作函数中始终接受 *args 和 **kwargs。
但是,此解决方案不起作用有两个原因:
object.__init__ 不接受参数
这是 python 2.6 / 3.x 引入的重大更改
TypeError: object.__init__() takes no parameters
使用*args 实际上会适得其反
解决方案 TL;DR
-
super() 的用法必须一致:在类层次结构中,super 应该在任何地方或任何地方都使用。是类合同的一部分。如果一个类使用super(),则所有类必须也以相同的方式使用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()
如果类层次结构中的重写函数可以采用不同的参数,请始终将收到的所有参数作为关键字参数传递给超级函数,并且始终接受**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)
输出:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
讨论
让我们更详细地研究这两个问题
object.__init__ 不接受参数
考虑 James Knight 给出的原始解决方案:
一般规则是:始终将收到的所有参数传递给超级函数,并且,如果类可以接受不同的参数,则始终接受 *args 和 **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)
输出:
MRO: ['E', 'C', 'A', 'D', 'B', 'SuperObject', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
SuperObject
使用*args 会适得其反
让示例稍微复杂一点,使用两个不同的参数:name 和 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)
输出:
E name=python age=28
C age=python
A
D name=python
B
SuperObject
从C age=python 行可以看出,位置参数变得混乱,我们传递了错误的东西。
我建议的解决方案是更严格,完全避免*args 参数。而是:
如果类可以采用不同的参数,请始终将收到的所有参数传递给超级函数作为关键字参数,并且始终接受**kwargs。
这里有一个基于这个更严格规则的解决方案。首先从SuperObject中删除*args
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)
输出:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
这是正确的