Статья Джеймса Найта super () считается вредной предлагает решение, всегда принимая *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__
не принимает аргументы
рассмотрим оригинальное решение, данное Джеймсом Найтом:
общее правило: всегда передавайте все полученные аргументы суперфункции, и, если классы могут принимать разные аргументы, всегда принимайте *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)
выход: * +1081 *
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
.
вот решение, основанное на этом более строгом правиле. сначала удалите *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
что правильно