Встроенные типы Python 3 __init__ не вызывают super () .__ init__? - PullRequest
15 голосов
/ 18 января 2012

При выводе из встроенного типа, а также из какого-либо другого класса создается впечатление, что конструктор встроенного типа не вызывает конструктор суперкласса.Это приводит к тому, что методы __init__ не вызываются для типов, следующих за встроенными в MRO.

Пример:

class A:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("A().__init__()")

class B(list, A):
    def __init__(self, *args, **kwargs):
        print("B().__init__() start")
        super().__init__(*args, **kwargs)
        print("B().__init__() end")

if __name__ == '__main__':
    b = B()

В этом примере A .__ init__ никогда не вызывается.Когда B определяется как class B(A, list) вместо этого - переключение порядка наследования - он работает как задумано (то есть вызывается A .__ init__).

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

Дополнительная информация: Python версия 3.1

1 Ответ

10 голосов
/ 18 января 2012

Правильное использование super() довольно тонкое и требует некоторой осторожности, если не все методы сотрудничества имеют одинаковую подпись. Обычный шаблон для __init__() методов следующий:

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

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

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

class D(B, C):
    def __init__(self, param_d, **kwargs):
        self.param_d = param_d
        super(D, self).__init__(**kwargs)

d = D(param_a=1, param_b=2, param_c=3, param_d=4)

Обратите внимание, что для этого необходимо, чтобы все методы сотрудничали, и чтобы все методы имели несколько совместимую подпись, чтобы гарантировать, что не имеет значения, в какой момент вызывается метод.

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

super() можно использовать только в том случае, если либо все методы сотрудничества имеют одинаковую подпись (например, __setattr__()), либо если вы используете вышеупомянутый (или аналогичный) шаблон. Однако использование super() - не единственный шаблон для вызова методов базового класса. Если в шаблоне множественного наследования нет «алмазов», вы можете использовать явные вызовы базового класса, например B.__init__(self, param_a). Классы с несколькими базовыми классами просто вызывают несколько конструкторов. Даже если есть алмазы, вы можете иногда использовать явные вызовы, если вы позаботитесь о том, чтобы __init__() можно было вызывать несколько раз без вреда.

Если вы все равно хотите использовать super() для конструкторов, вам действительно не следует использовать подклассы встроенных типов (за исключением object) в нескольких иерархиях наследования. Некоторое дальнейшее чтение:

...