#python #oop #inheritance #multiple-inheritance
#python #ооп #наследование #множественное наследование
Вопрос:
Насколько я понимаю, если все классы используют «новый стиль», то это произойдет:
class A(object):
def __init__(self):
print("Entering A")
super().__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super().__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super().__init__()
print("Leaving C")
c = C()
Entering C
Entering A
Entering B
Leaving B
Leaving A
Leaving C
После понимания того, что super
использует MRO исходного объекта ( c
), становится понятно, почему Entering/Leaving
они такие, какие они есть. Но что, если мне нужно передать некоторые конкретные аргументы в A
и B
, но также порядок, в котором аргументы принимаются в C
, не похож на A
и B
:
class A(object):
def __init__(self, x1, x2):
print(f"Entering A [x1={x1}, x2={x2}]")
super().__init__()
print("Leaving A")
class B(object):
def __init__(self, y1, y2):
print(f"Entering B [y1={y1}, y2={y2}]")
super().__init__()
print("Leaving B")
class C(A, B):
def __init__(self, x1, y1, x2, y2):
print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
super().__init__()
print("Leaving C")
c = C('x1', 'y1', 'x2', 'y2')
Я могу думать о том, чтобы просто проходить kwargs
весь день, но я чувствую, что может быть более приятный способ.
Кроме того, что, если B
это часть библиотеки, поэтому мы не можем ее изменить, и она использует позиционные аргументы, а не kwargs
? В таком случае, что я могу сделать, поскольку я не думаю, что это хорошая идея вручную возиться с MRO, но в то же время конструктор B
не примет kwargs
?
Каков правильный способ решения вышеуказанной проблемы?
Комментарии:
1. Я озадачен, вы хотели бы вызвать родительский конструктор, не зная, какой родительский элемент и какую подпись имеет конструктор спереди? Вы могли бы сделать это с помощью
kwargs
и получить нужные вам биты, если вам нужна гибкость, но «правильным» способом было бы вызватьA.__init__(self, x1, x2)
иB.__init__(self, y1, y2)
. Никакой магии, никакой двусмысленности.2. @OndrejK.но разве это не «старый стиль»? Я понимаю, как вы можете использовать этот стиль, но подразумевается, что «новый стиль» должен быть способен делать все, что может «старый стиль», и предоставлять некоторые дополнительные преимущества сверх этого (в данном случае более надежный код для факторизации).
3. Для того, что вы, кажется, описываете, это было бы не вопросом «старого стиля», а явным описанием дизайна / намерений.
4. @OndrejK.можете ли вы тогда объяснить, почему был введен этот «новый стиль», если он не способен выполнять те же задачи, что и «старый стиль»?
Ответ №1:
Хорошо, это больше не будет вписываться в комментарии, но, похоже, здесь есть некоторое недопонимание.
Для:
class C:
def __init__(self, arg1=1):
self.attr1 = arg1
class D(C):
def __init__(self, arg1):
super().__init__()
Тогда это дало бы:
d = D(2)
print(d.attr1) # prints: 1
Потому что через super().__init__()
мы вызвали родительский конструктор, но не передали ни одного аргумента, и он запустился со значением по умолчанию.
Или процитировать документы о том, что делает super:
Возвращает прокси-объект, который делегирует вызовы методов родительскому или родственному классу типа. Это полезно для доступа к унаследованным методам, которые были переопределены в классе.
Т. е. вы «только» получаете (возможно, связанный) разрешенный метод для вызова. Не больше, не меньше. Он не сортирует никакие аргументы, которые вы передаете. Таким образом, ваш пример на самом деле не сработает, потому что конструкторы обоих родителей ожидают экземпляр и еще два аргумента.
В вашем примере вы могли бы сказать:
class A:
def __init__(self, x1, x2):
print(f"Entering A [x1={x1}, x2={x2}]")
print("Leaving A")
class B:
def __init__(self, y1, y2):
print(f"Entering B [y1={y1}, y2={y2}]")
print("Leaving B")
class C(A, B):
def __init__(self, x1, y1, x2, y2):
print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
A.__init__(self, x1, x2)
B.__init__(self, y1, y2)
print("Leaving C")
Потому что вы вызываете каждый конкретный родительский конструкторс соответствующими конкретными аргументами. Документация (то же место, несколько абзацев ниже по второму распространенному варианту использования с множественным наследованием) также содержит подсказку относительно этого:
Хороший дизайн требует, чтобы этот метод имел одинаковую сигнатуру вызова в каждом случае (поскольку порядок вызовов определяется во время выполнения, поскольку этот порядок адаптируется к изменениям в иерархии классов и поскольку этот порядок может включать родственные классы, которые неизвестны до выполнения).
То есть вы получаете прокси для вызова метода … и вызываете его, используя его аргументы… который должен быть фиксированным / стабильным набором, поскольку вы не можете быть уверены заранее, какой метод вы используете через этот прокси.