Python 3 встроенных типов __init__ не вызывает super().__ init__?

При построении встроенного типа, а также из некоторого другого класса, конструктор встроенного типа не вызывает конструктор суперкласса. Это приводит к тому, что методы __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

Правильное использование 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) в нескольких иерархиях бездействия. Дальнейшее чтение: