poprawny sposób używania super (przekazywanie argumentów)

105

Więc podążałem za super uważanym za szkodliwym Pythona i poszedłem przetestować jego przykłady.

Jednak przykład 1-3 , który ma pokazać prawidłowy sposób wywołania superpodczas obsługi __init__metod, które oczekują różnych argumentów, nie działa.

Oto, co otrzymuję:

~ $ 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

Wydaje się, że objectsamo w sobie narusza jedną z najlepszych praktyk wymienionych w dokumencie, a mianowicie metody, które stosują supermuszą akceptować *argsi **kwargs.

Oczywiście pan Knight spodziewał się, że jego przykłady zadziałają, więc czy jest to coś, co zostało zmienione w ostatnich wersjach Pythona? Sprawdziłem 2.6 i 2.7 i na obu nie działa.

Jaki jest więc właściwy sposób rozwiązania tego problemu?

cha0site
źródło
2
Mój preferowany sposób to: płaskie i proste hierarchie dziedziczenia.
millimoose
20
Powinieneś także przeczytać "Super () w Pythonie uważane za super", aby uzyskać zrównoważony obraz :)
Björn Pollex
@ BjörnPollex: Dzięki! Twój link dostarcza odpowiedzi na pytanie: Możesz napisać "klasę główną", która dziedziczy po object, i upewni się, że wywołanie objectjest __init__poprawne.
cha0site
5
Zauważ, że __init__on objectpo cichu ignoruje wszelkie parametry w Pythonie 2.5. Zmieniło się to w Pythonie 2.6.
Wilfred Hughes
1
@Wilfred: Hej, dzięki za odpowiedź na właściwe pytanie! Teraz wiem, dlaczego esej jest nieaktualny!
cha0site

Odpowiedzi:

110

Czasami dwie klasy mogą mieć wspólne nazwy parametrów. W takim przypadku nie można zdjąć par klucz-wartość **kwargsani z nich usunąć *args. Zamiast tego możesz zdefiniować Baseklasę, która w przeciwieństwie do object, absorbuje / ignoruje argumenty:

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)

plony

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

Zauważ, że aby to zadziałało, Basemusi to być przedostatnia klasa w MRO.

unutbu
źródło
4
Nie powinienem Basedzwonić super(Base, self).__init__()?
cha0site
4
Aby to zadziałało, Basemusi nastąpić na końcu MRO (z wyjątkiem object). Dzwonienie object.__init__nic nie daje, więc dobrze jest nie dzwonić super(Base, self).__init__(). W rzeczywistości myślę, że może być jaśniejsze, aby nie uwzględniać super(Base, self).__init__()punktu, który Basejest końcem trasy.
unutbu
27

Jeśli masz zamiar mieć dużo dziedziczenia (tak jest w tym przypadku) proponuję przekazać wszystkie parametry za pomocą **kwargs, a następnie popje zaraz po ich użyciu (chyba że potrzebujesz ich w wyższych klasach).

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)

To najprostszy sposób rozwiązania tego rodzaju problemów.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
źródło
7

Jak wyjaśniono w metodzie super () Pythona uważanej za super , jednym ze sposobów jest zjadanie przez klasę argumentów, których wymaga, a resztę przekazuje dalej. Tak więc, gdy łańcuch wywołań osiągnie object, wszystkie argumenty zostały zjedzone i object.__init__zostaną wywołane bez argumentów (zgodnie z oczekiwaniami). Twój kod powinien więc wyglądać następująco:

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)
Björn Pollex
źródło
Więc o co chodzi E(arg = 10)? Nie podoba mi się ta metoda, muszę wcześniej znać MRO, aby z niej skorzystać. Druga metoda, w której piszę „klasę główną”, która dziedziczy po, objectwydaje się znacznie czystsza.
cha0site
Ta metoda jest przydatna, jeśli wywoływane funkcje nie współużytkują nazw argumentów. Przyznaję, że nie mam dużego doświadczenia praktycznego super, więc takie podejście rzeczywiście może być raczej teoretyczne.
Björn Pollex