Pythonic способ передачи разных аргументов в настройке множественного наследования - PullRequest
1 голос
/ 07 ноября 2019

Рассмотрим следующий фрагмент:

class A:
    def __init__(self, a):
        self._a = a

class B:
    def __init__(self, b1, b2):
        self._b1 = b1
        self._b2 = b2

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2, a=a)

Тогда я столкнусь со следующей ошибкой:

TypeError: init () получил неожиданный аргумент ключевого слова'a'

Чтобы решить эту проблему, я инициализирую суперкласс 'A' следующим образом:

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2)
        A.__init__(self, a=a)

Еще одно решение:

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2)
        super(B, self).__init__(a=a)

Iнашел эти решения методом проб и ошибок. Итак, мне интересно, каков самый элегантный способ передачи аргументов нескольким суперклассам.

1 Ответ

1 голос
/ 07 ноября 2019

Если у вас есть классы, которые будут составлены с другими классами, о которых они не должны знать (в частности, не нуждаются или не заботятся о своих аргументах), вы можете использовать ** Python Parameter /Синтаксис передачи аргументов для складывания и разворачивания аргументов в словарь.

Другими словами, ваш код будет выглядеть так:

class A:
    def __init__(self, a, **kwargs):
        self._a = a
        super().__init__(**kwargs)

class B:
    def __init__(self, b1, b2, **kwargs):
        self._b1 = b1
        self._b2 = b2
        super().__init__(**kwargs)

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2, a=a)

Также обратите внимание, что в ваших базовых классах __init__ код вышевы не вызывали super(), поэтому когда-нибудь будет выполняться только __init__ из ближайшего суперкласса.

super() в Python делает всю магию, необходимую для правильного работы множественного наследования. Одной из наиболее полных статей по этому вопросу по-прежнему является суперсчитаемый супер Python * .

Вкратце, что происходит: когда вы создаете класс, используя или не несколько родителей, Python создает __mro__ атрибут для него - это «порядок разрешения методов» для этого класса, указывающий порядок, в котором методы и атрибуты будут искать в предках.

Алгоритм вычисления самого MRO сложен, его немного объяснили, но ему можно разумно доверять, поскольку он "просто делает правильные вещи". До сегодняшнего дня единственное место, где я нашел его полное описание, это его оригинальная презентация в документации Python 2.3 , более 15 лет назад. (необязательное чтение, поскольку оно «делает правильные вещи».)

Что super() делает, это создает прокси-объект, который выберет следующий класс в последовательности «mro» для исходного вызывающего класса, иМетоды поиска прямо в его __dict__ - если он не найден, он перейдет к следующей записи в «mro». Итак, если вы рассмотрите только класс B, super().__init__() внутри его тела вызовет object.__init__. Если в этот момент «kwargs» пуст, как и в случае, если B() вызывается только с теми параметрами, о которых он заботится, это именно то, что нужно.

Когда B.__init__ выполняется в цепочечном super() вызове из класса "C", составленного из "B" и "A", кто бы то ни был, super() в B.__init__ будет использовать "C"MRO - и следующий класс -" A "- поэтому A.__init__ вызывается со всеми неиспользованными аргументами ключевого слова.

...