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 super
podczas 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 object
samo w sobie narusza jedną z najlepszych praktyk wymienionych w dokumencie, a mianowicie metody, które stosują super
muszą akceptować *args
i **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?
object
, i upewni się, że wywołanieobject
jest__init__
poprawne.__init__
onobject
po cichu ignoruje wszelkie parametry w Pythonie 2.5. Zmieniło się to w Pythonie 2.6.Odpowiedzi:
Czasami dwie klasy mogą mieć wspólne nazwy parametrów. W takim przypadku nie można zdjąć par klucz-wartość
**kwargs
ani z nich usunąć*args
. Zamiast tego możesz zdefiniowaćBase
klasę, która w przeciwieństwie doobject
, 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,
Base
musi to być przedostatnia klasa w MRO.źródło
Base
dzwonićsuper(Base, self).__init__()
?Base
musi nastąpić na końcu MRO (z wyjątkiemobject
). Dzwonienieobject.__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óryBase
jest końcem trasy.Jeśli masz zamiar mieć dużo dziedziczenia (tak jest w tym przypadku) proponuję przekazać wszystkie parametry za pomocą
**kwargs
, a następniepop
je 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)
źródło
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 iobject.__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)
źródło
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,object
wydaje się znacznie czystsza.super
, więc takie podejście rzeczywiście może być raczej teoretyczne.