NoneSuper () и изменение сигнатуры кооперативных методов

В настройках множественного наследования, таких как изложенные в, как я могу использовать super() а также обработать случай, когда сигнатура функции меняется между классами в иерархии?

т.е. я могу переписать этот пример (в python3) для работы с super()?

пример взят из статьи super() считается вредной статьей

class A():
    def __init__(self):
        print("A")

class B(object):
    def __init__(self):
        print("B")

class C(A):
    def __init__(self, arg):
        print("C","arg=",arg)
        A.__init__(self)

class D(B):
    def __init__(self, arg):
        print("D", "arg=",arg)
        B.__init__(self)

class E(C,D):
    def __init__(self, arg):
        print("E", "arg=",arg)
        C.__init__(self, arg)
        D.__init__(self, arg)

Е (10)

2 ответа

Статья Джеймса Найта, которую super() считает вредной, предлагает решение, всегда принимая *args а также **kwargs во всех взаимодействующих функциях. Однако это решение не работает по двум причинам:

  1. object.__init__ не принимает аргументы, это серьезное изменение введено Python 2.6 / 3.x TypeError: object.__init__() takes no parameters

  2. с помощью*argsна самом деле контрпродуктивно

Решение TL;DR

  1. super()использование должно быть последовательным: в иерархии классов super следует использовать везде или нигде. является частью договора класса. если один класс используетsuper()все классыДОЛЖНЫ также использовать super()таким же образом, или иначе мы могли бы вызывать определенные функции в иерархии ноль раз, или более одного раза

  2. правильно поддерживать__init__Функции с любыми параметрами, классы верхнего уровня в вашей иерархии должны наследовать от пользовательского класса, такого как SuperObject:

    class SuperObject:        
        def __init__(self, **kwargs):
            mro = type(self).__mro__
            assert mro[-1] is object
            if mro[-2] is not SuperObject:
                raise TypeError(
                    'all top-level classes in this hierarchy must inherit from SuperObject',
                    'the last class in the MRO should be SuperObject',
                    f'mro={[cls.__name__ for cls in mro]}'
                )
    
            # super().__init__ is guaranteed to be object.__init__        
            init = super().__init__
            init()
    
  3. если переопределенные функции в иерархии классов могут принимать разные аргументы, всегда передавайте все полученные аргументы суперфункции в качестве аргументов ключевых слов и всегда принимайте**kwargs,

Вот переписанный пример

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

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

e = E(name='python', age=28)

выход:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

обсуждение

давайте рассмотрим обе проблемы более подробно

object.__init__не принимает аргументы

рассмотрим оригинальное решение, данное Джеймсом Найтом:

общее правило: всегда передавайте все полученные аргументы суперфункции, и, если классы могут принимать разные аргументы, всегда принимайте*argsа также**kwargs,

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

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

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

    class D(B):
        def __init__(self, arg, *args, **kwargs):
            print("D", "arg=",arg)
            super().__init__(arg, *args, **kwargs)

    class E(C,D):
        def __init__(self, arg, *args, **kwargs):
            print( "E", "arg=",arg)
            super().__init__(arg, *args, **kwargs)

    print( "MRO:", [x.__name__ for x in E.__mro__])
    E(10)

последние изменения в Python 2.6 и 3.x изменились object.__init__ подпись, чтобы она больше не принимала произвольные аргументы

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-9001c741f80d> in <module>
     25 
     26 print( "MRO:", [x.__name__ for x in E.__mro__])
---> 27 E(10)

...

<ipython-input-2-9001c741f80d> in __init__(self, *args, **kwargs)
      7     def __init__(self, *args, **kwargs):
      8         print("B")
----> 9         super(B, self).__init__(*args, **kwargs)
     10 
     11 class C(A):

TypeError: object.__init__() takes exactly one argument (the instance to initialize)

Правильный способ справиться с этой загадкой - наследовать классы верхнего уровня в иерархии от пользовательского класса, такого как SuperObject:

class SuperObject:        
    def __init__(self, *args, **kwargs):
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

и, таким образом, переписать пример следующим образом должно работать

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

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

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

    class D(B):
        def __init__(self, arg, *args, **kwargs):
            print("D", "arg=",arg)
            super(D, self).__init__(arg, *args, **kwargs)

    class E(C,D):
        def __init__(self, arg, *args, **kwargs):
            print( "E", "arg=",arg)
            super(E, self).__init__(arg, *args, **kwargs)

    print( "MRO:", [x.__name__ for x in E.__mro__])
    E(10)

выход:

MRO: ['E', 'C', 'A', 'D', 'B', 'SuperObject', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
SuperObject

с помощью *args контрпродуктивно

Давайте сделаем пример немного сложнее, с двумя разными параметрами: name а также age

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

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

class C(A):
    def __init__(self, age, *args, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age, *args, **kwargs)

class D(B):
    def __init__(self, name, *args, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name, *args, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name, age, *args, **kwargs)

E('python', 28)

выход:

E name=python age=28
C age=python
A
D name=python
B
SuperObject

как вы можете видеть из строки C age=python позиционные аргументы запутались, и мы передаем не то, что нужно.

Мое предлагаемое решение должно быть более строгим и избегать *args аргумент в целом. вместо:

если классы могут принимать разные аргументы, всегда передавайте все полученные аргументы суперфункции в качестве аргументов с ключевыми словами и всегда принимайте **kwargs,

Вот решение, основанное на этом более строгом правиле. сначала удалить *args из SuperObject

class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

а теперь удали *args от остальных классов, и передавать аргументы только по имени

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

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

выход:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

что правильно

Пожалуйста, посмотрите следующий код, это отвечает на ваш вопрос?

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

class B():
    def __init__(self, *args, **kwargs):
        print("B")

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

class D(B):
    def __init__(self, *args, **kwargs):
        print("D", "arg=", *args)
        super().__init__(self, *args, **kwargs)

class E(C,D):
    def __init__(self, *args, **kwargs):
        print("E", "arg=", *args)
        super().__init__(self, *args, **kwargs)


# now you can call the classes with a variable amount of arguments
# which are also delegated to the parent classed through the super() calls
a = A(5, 5)
b = B(4, 4)
c = C(1, 2, 4, happy=True)
d = D(1, 3, 2)
e = E(1, 4, 5, 5, 5, 5, value=4)
Другие вопросы по тегам