У меня возникла проблема с использованием множественного наследования в Python. В моем коде дочерний класс A наследуется от двух родительских классов, B и C, которые являются абстрактными базовыми классами. Каждый из родительских классов, B и C, наследуется от общего родительского (или дедушки) класса D. Класс D также является абстрактным базовым классом.
Создание экземпляра моего дочернего класса генерирует следующую ошибку:
TypeError: __main__.D.__init__() got multiple values for keyword argument 'd'
Мне кажется, это может быть связано с классической проблемой алмазов?
class D
/ \
class B class C
\ /
class A
Но, насколько я понимаю, порядок разрешения методов Python должен решить эту проблему.
Отображение MRO для каждого класса:
print(f"MRO: {[x.__name__ for x in A.__mro__]}")
print(f"MRO: {[x.__name__ for x in B.__mro__]}")
print(f"MRO: {[x.__name__ for x in C.__mro__]}")
print(f"MRO: {[x.__name__ for x in D.__mro__]}")
возвращает именно то, что я ожидал увидеть:
MRO: ['A', 'B', 'C', 'D', 'ABC', 'object']
MRO: ['B', 'D', 'ABC', 'object']
MRO: ['C', 'D', 'ABC', 'object']
MRO: ['D', 'ABC', 'object']
Этот минимальный блок кода воспроизводит проблему:
from abc import ABC, abstractmethod
class D(ABC,):
def __init__(
self,
_D__arg=None,
*args,
**kwargs,
):
self.arg = _D__arg
super(D, self).__init__(*args, **kwargs,)
@abstractmethod
def d_abstract_base_class(self):
pass
class C(D, ABC,):
def __init__(
self,
_C__arg=None,
*args,
**kwargs,
):
self.arg = _C__arg
super(C, self).__init__(
_D__arg=self.arg,
*args,
**kwargs,
)
def d_abstract_base_class(self):
return True
@abstractmethod
def c_abstract_base_class(self):
pass
class B(D, ABC,):
def __init__(
self,
_B__arg=None,
*args,
**kwargs,
):
self.arg = _B__arg
super(B, self).__init__(
_D__arg=self.arg,
*args,
**kwargs,
)
def d_abstract_base_class(self):
return True
@abstractmethod
def b_abstract_base_class(self):
pass
class A(B,C,):
def __init__(
self,
_A__arg=None,
*args,
**kwargs,
):
self.arg = _A__arg
super(A, self).__init__(
_B__arg=self.arg,
_C__arg=self.arg,
*args,
**kwargs,
)
def b_abstract_base_class(self):
return True
def c_abstract_base_class(self):
return True
a = A(a=1,)
Подобные вопросы, относящиеся к этой ошибке в SO, по-видимому, не касаются множественного наследования как основной причины.
В приведенном выше блоке кода, похоже, нет каких-либо распространенных проблем, рассматриваемых в этих связанных вопросах, включая: отсутствие аргумента «self» в определениях методов, конфликты позиционных/ключевых аргументов, неупорядоченные позиционные аргументы и т. д.
редактировать:
InSync предложил этот более ранний вопрос:
Что делает «супер» в Python?
в котором много полезной информации, отличные объяснения, и его определенно стоит прочитать! Однако, похоже, он не отвечает на этот конкретный вопрос, поскольку мой код уже использует super. Я попробовал переключиться на подход Python3 для вызова super:
super().__init__() что устранило ошибку, но не передало аргументы родительским классам — и это одна из причин, по которой я пытаюсь это сделать.
Я неправильно понимаю множественное наследование в Python? Или какая-то более общая концепция Pythonic?
Если это поможет:
эй @mkrieger1, я отредактирую и упрощу
B.__init__() вызывает C.__init__() с помощью _D__arg_0 = 1, что также C.__init__() делает с D.__init__(). Если все MRO соответствуют ожиданиям, о чем этот вопрос?
Отвечает ли это на ваш вопрос? Что делает «супер» в Python? - разница между super().__init__() и явным суперклассом __init__()
Если это актуально, Пылинт дает мне «Аргумент ключевого слова перед списком переменных позиционных аргументов в определении функции __init__ (W1113:keyword-arg-before-vararg)»
Не говоря уже о том, что Пылинт также дает мне «Переопределение имени 'a' из внешней области (W0621:redefine-outer-name)» в a=2 в A.__init__. Вы можете переименовать A(a=1) так, a_instance или что-то в этом роде. a_instance = A(a=1)
@wjandrea хорошая мысль. я обновляю код, чтобы внести ясность в ответ на различные комментарии
@InSync в ответ на ваш предыдущий комментарий, MRO соответствуют ожиданиям, что заставило меня поверить, что разрешение атрибутов в этой ситуации множественного наследования будет обработано. Однако (и именно в этом вопрос) Python генерирует ошибку «Typedef: получено несколько значений для аргумента ключевого слова».






Неправильная часть этого кода — это именно то, что написано в сообщении об ошибке: Вы можете передать аргумент либо как позиционный, либо как именованный, но не как то и другое.
И вы делаете свои вызовы, вставляя явные аргументы nam в параметры c, b и d, помещая любые аргументы, определенные позицией.
На самом деле, поскольку у вас есть несовпадающие начальные параметры с вашими подклассами, идеальным для вас было бы вообще не использовать позиционные аргументы, а просто требовать, чтобы любые аргументы любого подкласса были названы: это позволит избежать большой головной боли. . Тем не менее, там может быть место для анонимных позиционных параметров, если вы передаете их в правильном порядке. (передайте первый аргумент, будь то c, d или что-то еще, только как позиционный, тогда *args).
Также обратите внимание, что классы B и C не могут «знать», что они вызывают «D» в super() (один из них не будет при создании экземпляра a), и вы передаете явные конфликтующие значения в аргумент d.
Тем не менее, позвольте мне переписать что-то похожее на ваш пример (за исключением лишних абстрактных методов, поскольку они ничего не добавляют к вопросу) так, чтобы это работало.
from abc import ABC, abstractmethod
class D(ABC,):
def __init__(self, d=None, *args, **kwargs):
# By declaring the first argument as either named or positional with
# a default, any call here _EITHER_ has `d` as a named argument
# _OR_ has any positional parameters. One can't have both.
# we are better not allowing any positional only arguments (doing
# that for the following classes):
self.d = d
super().__init__(*args, **kwargs,)
def __repr__(self):
return f"Instance of {self.__class__.__name__} with attrs: {self.__dict__}"
class C(D):
def __init__(self, c=None, *, **kwargs):
self.c = c
# Note that the immediate super() class is determined at
# runtime. If there is another "d" value incomming from a super() call
# from "B.__init__" you will get an error message.
# so you can't do:
#super().__init__(d=self.c, **kwargs,)
# But this is possible and will override any other value for "d"
kwargs["d"] = self.c
super().__init__(**kwargs)
class B(D):
def __init__(self, b=None, *, **kwargs):
self.b = b
kwargs["d"] = self.b # (may be overriden if C is in the MRO)
super().__init__(**kwargs,)
class A(B,C):
def __init__(self, a=2, *, **kwargs):
self.a = a
kwargs["c"] = kwargs["b"] = self.a
# alternatively, just add c and b values if they are not given:
# if "c" not in kwargs: kwargs["c"] = self.a
# ...
super(A, self).__init__(**kwargs,)
a = A(a=1,)
спасибо за ответ, jsbueno, пара вещей. все аргументы должны быть ключевыми словами. код не должен передавать ничего позиционно.
хороший. В примере уже удалены все позиционные аргументы ключевого слова, кроме определения D. Кроме того, если вы хотите разрешить явную передачу параметров «b, c, d» при создании A, вам необходимо использовать шаблон, закомментированный для аргумента c в A.__init__, во всех классах. (если кода много, его всегда можно сократить)
фактический (не упрощенный) код имеет совпадающие имена аргументов в дочерних, родительских, родительских и т. д. классах. в первоначальной версии кода, включенного в этот вопрос, использовался подход, который я использовал для решения этой проблемы в коде «чтения»: использование чего-то вроде «_A__a» в качестве имени аргумента для ключевого слова arg класса A. код был упрощен, чтобы улучшить читаемость. похоже, я слишком упростил :) собираюсь исправить.
просматривая ваше решение более внимательно, похоже, это может решить мою проблему. Поначалу я не решался явно нажимать на кваргов, но, похоже, это то, что мне следовало делать с самого начала. ты!
Множество подчеркиваний, нетрадиционные пробелы (или отсутствие пробелов) и запятые делают это излишне трудным для чтения. Можете ли вы упростить имена и форматирование (может быть, использовать автоматическое форматирование кода, такое как черный)?