Множественное наследование с помощью kwargs - PullRequest
6 голосов
/ 02 марта 2020

Проблема

Я сталкивался с этим кодом в Объектно-ориентированном программировании на Пыльный Филлипс (упрощенный для краткости), и я не уверен насчет определенной части c это определение.

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

class B:
    def __init__(self, b, **kwargs):
        super().__init__(**kwargs)
        self.b = b

class C(A, B):
    def __init__(self, c, **kwargs):
        super().__init__(**kwargs)
        self.c = c

Вопросы

  1. Поскольку порядок разрешения метода равен (__main__.C, __main__.A, __main__.B, object), можно ли вместо этого class B определить следующим образом?
class B:
    def __init__(self, b):
        self.b = b
Не является ли super().__init__(**kwargs) в class B избыточным, поскольку любой излишек kwargs, переданный в C, будет передан в object с повышением?

TypeError: object .__ init __ () принимает ровно один аргумент (экземпляр для инициализации)

Это гарантия того, что C было определено как class C(B, A) вместо class C(A, B)?

Ответы [ 3 ]

3 голосов
/ 02 марта 2020

Рассмотрим, как вы можете создать экземпляр C:

c = C(a=3, b=5, c=9)

C.__init__ получает все аргументы ключевого слова, но использует только один для своего собственного параметра c. Остальные передаются для следующего __init__ метода в цепочке. В данном случае это A.__init__, который "вытягивает" аргумент для a и передает b в B.__init__. B использует это и передает (теперь пустой) набор аргументов ключевого слова методу next , object.__init__. Поскольку все аргументы ключевых слов были «заявлены» и обработаны другими классами, object.__init__ завершается успешно.

Из-за особенностей построения MRO классы, которые правильно используют super(), гарантируют коллективно , что **kwargs будет пустым ко времени вызова object.__init__.

2 голосов
/ 02 марта 2020

В этом примере B работал бы так же, если бы он был определен так, как вы говорите (пункт 1), и C такой, как есть (и его использование не используется).

Как для пункта 2: вызов конструктора super() действительно завершился бы неудачно, как указано, если бы оставались еще ключевые аргументы, например:

c = C(a=1, b=2, c=3, d=4)
# -> TypeError: object.__init__() takes exactly one argument (the instance to initialize)

Поскольку класс B (изначально) записан, он было бы также хорошо, если бы вы использовали в обратном порядке, как вы говорите, или если был один (или) более суперкласс (ы), например:

class D:
    def __init__(self, d, **kwargs):
        super().__init__(**kwargs)
        self.d = d

class C(A,B,D):
    ...
1 голос
/ 02 марта 2020

Поскольку порядок разрешения метода равен (__main__.C, __main__.A, __main__.B, object), можно ли вместо этого определить class B следующим образом?

Нет, потому что тогда это не будет выполнено:

class D:
    def __init__(self, d, **kwargs):
        self.d = d
        super().__init__(**kwargs)

class E(C, D):
    def __init__(self, e, **kwargs):
        self.e = e
        super().__init__(**kwargs)

MRO E равно (E, C, A, B, D, object), поэтому B должен вызвать super().__init__, иначе D.__init__ не будет вызвано.

Не super().__init__(**kwargs) in class B избыточно, поскольку любой излишек kwargs, переданный в C, будет передан в object, повышая?

Нет, потому что излишек kwargs будет go до D.__init__ в приведенном выше примере. Но даже без этого нет необходимости выдавать ошибку, когда вы вызываете конструктор со слишком большим количеством аргументов; Желательно иметь сообщение об ошибке, информирующее вас о неверном коде, а не об ошибке go незамеченной.

Является ли это защитой, если C было определено как class C(B, A) вместо class C(A, B)?

В некотором смысле, конечно; но на самом деле это гарантия для B, встречающаяся в любой иерархии классов, при условии, что другие классы в иерархии следуют тому же правилу вызова super().__init__(**kwargs).

...