2012-01-23 15 views
52

Więc podążałem za Python's Super Considered Harmful i poszedłem przetestować jego przykłady.poprawny sposób korzystania z super (przekazywanie argumentów)

Jednak, Example 1-3, który ma pokazać prawidłowy sposób wywoływania super podczas obsługi metod, które oczekują różnych argumentów, nie działa.

To co mam:

~ $ 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 sama object narusza jeden z najlepszych praktyk wymienionych w dokumencie, który jest, że metody, które wykorzystują super musi zaakceptować *args i **kwargs.

Oczywiście Pan Knight spodziewał się, że jego przykłady zadziałają, więc czy to coś zmieniło się w ostatnich wersjach Pythona? Sprawdziłem 2.6 i 2.7, i nie udało się ich obu.

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

+2

Mój preferowany sposób to: Płaskie i proste hierarchie dziedziczenia. – millimoose

+14

Powinieneś również przeczytać ["Python's super() za super"] (http://rhettinger.wordpress.com/2011/05/26/super-considered-super/), aby uzyskać zrównoważony widok :) –

+0

@ BjörnPollex: Dzięki! Twój link zawiera odpowiedź na pytanie: Możesz napisać "klasę root", która dziedziczy po 'object', i upewnia się, że poprawnie wywołałeś' object'' __init__'. – cha0site

Odpowiedz

55

Czasami dwie klasy mogą mieć wspólne nazwy parametrów. W takim przypadku nie można pop pary klucz-wartość off **kwargs lub usunąć je z *args. Zamiast tego można zdefiniować Base klasę, która w przeciwieństwie do object, pochłania/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) 

daje

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

Zauważ, że to zadziałało, Base musi być przedostatni klasa w MRO.

+2

Nie powinien wywoływać 'Base'' super (Base, self) .__ init __() '? – cha0site

+2

Aby to działało, 'Base' musi pochodzić na końcu MRO (z wyjątkiem' obiektu'). Wywołanie 'obiektu .__ init__' nic nie robi, więc nie można wywoływać' super (Base, self) .__ init __() '. W rzeczywistości, myślę, że może być czystsze, aby nie uwzględniać 'super (Base, self) .__ init __()' aby dojechać do punktu, w którym 'Base' jest końcem linii. – unutbu

5

Jak wyjaśniono w Python's super() considered super, jednym ze sposobów jest, aby klasa spożywa argumenty, których wymaga, i przekazuje resztę. Tak więc, gdy łańcuch wywołania osiągnie object, wszystkie argumenty zostały zjedzone, a object.__init__ zostanie wywołany bez argumentów (tak jak oczekuje). Więc kod powinien wyglądać tak:

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) 
+0

A co z 'E (arg = 10)'? Nie podoba mi się ta metoda, muszę znać MRO z góry, aby z niej skorzystać. Druga metoda, w której napiszę "klasę root", która dziedziczy po 'obiekcie ', wydaje się znacznie czystsza. – cha0site

+0

Ta metoda jest przydatna, jeśli wywoływane funkcje nie współdzielą nazw argumentów. Przyznaję, że nie mam dużego praktycznego doświadczenia z 'super', więc takie podejście może być raczej teoretyczne. –

14

Jeśli masz zamiar mieć wiele dziedziczenia (tak jest w przypadku tutaj) Proponuję, aby przekazać wszystkie parametry za pomocą **kwargs, a następnie pop je tuż po użyciu (chyba że ich potrzebujesz 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 na rozwiązanie tego rodzaju problemów.

third = Third(first_arg=1, second_arg=2, third_arg=3) 
Powiązane problemy