korrekte Verwendung von Super (Argumentübergabe)

99

Also folgte ich Pythons Super Considered Harmful und ging, um seine Beispiele zu testen.

Jedoch Beispiel 1-3 , die die richtige Art und Weise des Aufrufs zeigen , sollte superbeim Umgang mit __init__Methoden , die unterschiedlichen Argumente erwarten, flat-out funktioniert nicht.

Das bekomme ich:

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

Es scheint, dass objectselbst eine der im Dokument erwähnten Best Practices verletzt wird, nämlich die Methoden, die verwenden supermüssen *argsund **kwargs.

Offensichtlich hat Mr. Knight erwartet, dass seine Beispiele funktionieren. Wurde dies in den letzten Versionen von Python geändert? Ich habe 2.6 und 2.7 überprüft und es schlägt bei beiden fehl.

Was ist der richtige Weg, um mit diesem Problem umzugehen?

cha0site
quelle
2
Mein bevorzugter Weg ist: Flache und einfache Vererbungshierarchien.
Millimoose
19
Sie sollten auch "Pythons super () als super angesehen" lesen , um eine ausgewogene Ansicht zu erhalten :)
Björn Pollex
@ BjörnPollex: Danke! Ihr Link gibt eine Antwort auf die Frage: Sie können eine "Stammklasse" schreiben, von der erbt object, und sie stellt sicher, dass die objectAufrufe __init__korrekt sind.
Cha0site
4
Beachten Sie, dass __init__on im objectHintergrund alle Parameter in Python 2.5 ignoriert. Dies hat sich in Python 2.6 geändert.
Wilfred Hughes
1
@ Wilfred: Hey, danke für die Beantwortung der eigentlichen Frage! Jetzt weiß ich, warum der Aufsatz veraltet ist!
Cha0site

Antworten:

103

Manchmal haben zwei Klassen einige gemeinsame Parameternamen. In diesem Fall können Sie die Schlüssel-Wert-Paare nicht **kwargsentfernen oder entfernen *args. Stattdessen können Sie eine BaseKlasse definieren , die im Gegensatz zu objectArgumenten Argumente absorbiert / ignoriert:

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)

ergibt

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

Beachten Sie, dass dies Basedie vorletzte Klasse in der MRO sein muss , damit dies funktioniert .

unutbu
quelle
4
Sollte nicht Baseanrufen super(Base, self).__init__()?
Cha0site
4
Damit dies funktioniert, Basemuss am Ende der MRO kommen (außer für object). Anrufen object.__init__bewirkt nichts, daher ist es in Ordnung, nicht anzurufen super(Base, self).__init__(). Tatsächlich denke ich, dass es klarer sein kann, super(Base, self).__init__()den Punkt, der Basedas Ende der Linie ist, nicht einzubeziehen, um nach Hause zu fahren .
Unutbu
25

Wenn Sie viel Vererbung haben werden (das ist hier der Fall), empfehle ich Ihnen, alle Parameter mit **kwargsund dann popdirekt nach der Verwendung zu übergeben (es sei denn, Sie benötigen sie in der Oberschicht).

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)

Dies ist der einfachste Weg, um diese Art von Problemen zu lösen.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
quelle
6

Wie in Pythons super () als super erklärt , besteht eine Möglichkeit darin, die Klasse die erforderlichen Argumente essen zu lassen und den Rest weiterzugeben. Wenn die Aufrufkette erreicht ist object, wurden alle Argumente verzehrt und object.__init__werden (wie erwartet) ohne Argumente aufgerufen. Ihr Code sollte also folgendermaßen aussehen:

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
quelle
Also, was ist mit E(arg = 10)? Ich mag diese Methode nicht, ich muss den MRO im Voraus kennen, um sie verwenden zu können. Die andere Methode, bei der ich eine "Stammklasse" schreibe, von der erbt, objectscheint viel sauberer zu sein.
Cha0site
Diese Methode ist nützlich, wenn die aufgerufenen Funktionen keine Argumentnamen gemeinsam haben. Ich gebe zu, dass ich nicht viel praktische Erfahrung damit habe super, daher könnte dieser Ansatz tatsächlich eher theoretisch sein.
Björn Pollex