NoneSuper () и изменение сигнатуры кооперативных методов
В настройках множественного наследования, таких как изложенные в, как я могу использовать super()
а также обработать случай, когда сигнатура функции меняется между классами в иерархии?
т.е. я могу переписать этот пример (в python3) для работы с super()
?
пример взят из статьи 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)
Е (10)
2 ответа
Статья Джеймса Найта, которую super() считает вредной, предлагает решение, всегда принимая *args
а также **kwargs
во всех взаимодействующих функциях. Однако это решение не работает по двум причинам:
object.__init__
не принимает аргументы, это серьезное изменение введено Python 2.6 / 3.xTypeError: 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)
выход:
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
что правильно
Пожалуйста, посмотрите следующий код, это отвечает на ваш вопрос?
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)