super () и изменение сигнатуры кооперативных методов - PullRequest
0 голосов
/ 22 июня 2019

в настройке множественного наследования, такой как изложенная в, как я могу использовать 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 ]

0 голосов
/ 22 июня 2019

Статья Джеймса Найта 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)

выход: * +1081 *

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

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

0 голосов
/ 22 июня 2019

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

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...