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

105

Итак, я следил за Python «Super Считается вредным» и пошел проверять его примеры.

Однако пример 1-3 , который должен показать правильный способ вызова superпри обработке __init__методов, которые ожидают разные аргументы, категорически не работает.

Вот что я получаю:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Кажется, что это objectсамо по себе нарушает одну из лучших практик, упомянутых в документе, а именно методы, которые используются, superдолжны принимать *argsи **kwargs.

Очевидно, мистер Найт ожидал, что его примеры будут работать, так что изменилось ли это в последних версиях Python? Я проверил 2.6 и 2.7, и на обоих ничего не вышло.

Итак, как правильно решить эту проблему?

cha0site
источник
2
Мой предпочтительный способ: плоские и простые иерархии наследования.
millimoose
20
Вы также должны прочитать «Python super () считается супер», чтобы получить сбалансированное представление :)
Björn Pollex
@ BjörnPollex: Спасибо! Ваша ссылка дает ответ на вопрос: Вы можете написать «корневой класс» , который наследует от object, и это гарантирует , что на призыв object«S __init__правильно.
cha0site
5
Обратите внимание, что __init__on objectмолча игнорирует любые параметры в Python 2.5. Это изменилось в Python 2.6.
Уилфред Хьюз
1
@Wilfred: Привет, спасибо за ответ на вопрос! Теперь я знаю, почему эссе устарело!
cha0site

Ответы:

111

Иногда у двух классов могут быть общие имена параметров. В этом случае вы не можете **kwargsудалить пары ключ-значение или удалить их *args. Вместо этого вы можете определить Baseкласс, который, в отличие от object, поглощает / игнорирует аргументы:

class Base(object):
    def __init__(self, *args, **kwargs): pass

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

class B(Base):
    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', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Обратите внимание, что для этого Baseдолжен быть предпоследний класс в MRO.

Unutbu
источник
4
Не надо Baseзвонить super(Base, self).__init__()?
cha0site
4
Чтобы это сработало, Baseнеобходимо указать конец MRO (кроме object). Звонки object.__init__ничего не делают, поэтому не звонить - это нормально super(Base, self).__init__(). На самом деле, я думаю, может быть более понятным не включать super(Base, self).__init__()в рассмотрение точку, которая Baseявляется концом линии.
unutbu
27

Если у вас будет много наследственности (как в данном случае), я предлагаю вам передать все параметры using **kwargs, а затем popих сразу после их использования (если они вам не нужны в высших классах).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

Это самый простой способ решить подобные проблемы.

third = Third(first_arg=1, second_arg=2, third_arg=3)
юлиомалегрия
источник
7

Как объясняется в Python super (), который считается супер , один из способов - заставить класс съесть требуемые аргументы и передать остальные. Таким образом, когда цепочка вызовов достигает object, все аргументы съедены и object.__init__будут вызываться без аргументов (как и ожидалось). Итак, ваш код должен выглядеть так:

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

class B(object):
    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__(*args, **kwargs)

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

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

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Бьорн Поллекс
источник
Так что насчет E(arg = 10)? Мне этот метод не нравится, мне нужно заранее знать MRO, чтобы им пользоваться. Другой метод, в котором я пишу «корневой класс», унаследованный от него, objectкажется намного чище.
cha0site
Этот метод полезен, если вызываемые функции не имеют общих имен аргументов. Я признаю, что у меня нет большого практического опыта super, поэтому этот подход действительно может быть скорее теоретическим.
Björn Pollex