Множественное наследование Python генерирует «TypeError: получено несколько значений для аргумента ключевого слова»

У меня возникла проблема с использованием множественного наследования в 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?

Если это поможет:

  • Питон 3.11.6
  • Убунту 24.04 ЛТС

Множество подчеркиваний, нетрадиционные пробелы (или отсутствие пробелов) и запятые делают это излишне трудным для чтения. Можете ли вы упростить имена и форматирование (может быть, использовать автоматическое форматирование кода, такое как черный)?

mkrieger1 04.06.2024 00:12

эй @mkrieger1, я отредактирую и упрощу

Overtoad 04.06.2024 00:14
B.__init__() вызывает C.__init__() с помощью _D__arg_0 = 1, что также C.__init__() делает с D.__init__(). Если все MRO соответствуют ожиданиям, о чем этот вопрос?
InSync 04.06.2024 00:23

Если это актуально, Пылинт дает мне «Аргумент ключевого слова перед списком переменных позиционных аргументов в определении функции __init__ (W1113:keyword-arg-before-vararg

wjandrea 04.06.2024 01:07

Не говоря уже о том, что Пылинт также дает мне «Переопределение имени 'a' из внешней области (W0621:redefine-outer-name)» в a=2 в A.__init__. Вы можете переименовать A(a=1) так, a_instance или что-то в этом роде. a_instance = A(a=1)

wjandrea 04.06.2024 01:09

@wjandrea хорошая мысль. я обновляю код, чтобы внести ясность в ответ на различные комментарии

Overtoad 04.06.2024 01:29

@InSync в ответ на ваш предыдущий комментарий, MRO соответствуют ожиданиям, что заставило меня поверить, что разрешение атрибутов в этой ситуации множественного наследования будет обработано. Однако (и именно в этом вопрос) Python генерирует ошибку «Typedef: получено несколько значений для аргумента ключевого слова».

Overtoad 04.06.2024 01:41
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
101
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Неправильная часть этого кода — это именно то, что написано в сообщении об ошибке: Вы можете передать аргумент либо как позиционный, либо как именованный, но не как то и другое.

И вы делаете свои вызовы, вставляя явные аргументы 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, пара вещей. все аргументы должны быть ключевыми словами. код не должен передавать ничего позиционно.

Overtoad 04.06.2024 01:18

хороший. В примере уже удалены все позиционные аргументы ключевого слова, кроме определения D. Кроме того, если вы хотите разрешить явную передачу параметров «b, c, d» при создании A, вам необходимо использовать шаблон, закомментированный для аргумента c в A.__init__, во всех классах. (если кода много, его всегда можно сократить)

jsbueno 04.06.2024 01:26

фактический (не упрощенный) код имеет совпадающие имена аргументов в дочерних, родительских, родительских и т. д. классах. в первоначальной версии кода, включенного в этот вопрос, использовался подход, который я использовал для решения этой проблемы в коде «чтения»: использование чего-то вроде «_A__a» в качестве имени аргумента для ключевого слова arg класса A. код был упрощен, чтобы улучшить читаемость. похоже, я слишком упростил :) собираюсь исправить.

Overtoad 04.06.2024 01:26

просматривая ваше решение более внимательно, похоже, это может решить мою проблему. Поначалу я не решался явно нажимать на кваргов, но, похоже, это то, что мне следовало делать с самого начала. ты!

Overtoad 04.06.2024 03:50

Другие вопросы по теме

Попытка отсортировать таблицу: TypeError: невозможно прочитать свойства неопределенного значения (чтение «textContent»)
Ошибка типа: Tasks.map не является функцией (стек MERN)
Невозможно открыть файл Rnw после обновления R до 4.4.0 - (TypeError): невозможно прочитать свойства неопределенного значения (чтение `count`)
Разработка API с помощью Oak от Deno: TypeError — request.body не является функцией
Ошибка Webpack 5 после успешной сборки! (Необнаруженная ошибка выполнения Inpage.js)
TypeError при попытке запустить версию Flutter Web для разработчиков
PyCharm предупреждает: «Ожидаемый тип 'int', получено 'float'» для случайного.randint, но код все равно иногда запускается
Необработанная ошибка времени выполнения TypeError: невозможно прочитать свойства null (чтение «по умолчанию») в моем next.js
Ошибка типа: невозможно прочитать свойства неопределенного значения (чтение «карты») в реакции разработки
Использование pysnmp-lextudio next() с генератором getCmd() приводит к TypeError: объект «кортеж» не является итератором