在诸如布局的多重继承设置中,如何使用super()
并处理函数的签名在层次结构中的类之间更改的情况?
即我可以重写此示例(在python3中)以与super()
一起使用吗?
class A():
def __init__(self):
print("A")
class B(object):
def __init__(self):
print("B")
class C(A):
def __init__(self, arg):
print("C","arg=",arg)
A.__init__(self)
class D(B):
def __init__(self, arg):
print("D", "arg=",arg)
B.__init__(self)
class E(C,D):
def __init__(self, arg):
print("E", "arg=",arg)
C.__init__(self, arg)
D.__init__(self, arg)
E(10)
答案 0 :(得分:-1)
James Knight的文章super() considered harmful提出了一种解决方案,即在所有协作功能中始终接受*args
和**kwargs
。
但是,此解决方案无法工作的原因有两个:
object.__init__
不接受参数
这是python 2.6 / 3.x引入的重大变化
TypeError: object.__init__() takes no parameters
使用*args
实际上会适得其反
super()
的用法必须保持一致:在类层次结构中,super应该在任何地方或任何地方使用。是班级合同的一部分。如果一个类使用super()
,所有类必须也都以相同的方式使用super()
,否则我们可能将层次结构中的某些函数调用0次,或多次调用< / p>
要正确支持带有任何参数的__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
会产生反作用 let使用两个不同的参数使示例更加复杂: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
。
这是基于此更严格规则的解决方案。首先从*args
删除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)
输出:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
这是正确的
答案 1 :(得分:-2)
请参见以下代码,这是否回答了您的问题?
class A():
def __init__(self, *args, **kwargs):
print("A")
class B():
def __init__(self, *args, **kwargs):
print("B")
class C(A):
def __init__(self, *args, **kwargs):
print("C","arg=", *args)
super().__init__(self, *args, **kwargs)
class D(B):
def __init__(self, *args, **kwargs):
print("D", "arg=", *args)
super().__init__(self, *args, **kwargs)
class E(C,D):
def __init__(self, *args, **kwargs):
print("E", "arg=", *args)
super().__init__(self, *args, **kwargs)
# now you can call the classes with a variable amount of arguments
# which are also delegated to the parent classed through the super() calls
a = A(5, 5)
b = B(4, 4)
c = C(1, 2, 4, happy=True)
d = D(1, 3, 2)
e = E(1, 4, 5, 5, 5, 5, value=4)