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?


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.


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)

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)

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.