13

Also, ich habe eine große Anzahl von Nachrichten Payload-Klassen für eine serielle API, von denen jede eine Reihe von unveränderlichen Feldern, eine Analyse-Methode und einige Methoden, die freigegeben sind. Die Art, wie ich dies strukturiere, ist, dass jeder von einem namedtuple für das Feldverhalten erbt und die üblichen Methoden von einer Elternklasse erhält. Aber ich habe einige Schwierigkeiten mit den Konstrukteuren mit:Wie rufe ich in Python die Superklasse auf, wenn es sich um ein einmal benanntes Tupel handelt?

class Payload: 
    def test(self): 
     print("bar") 

class DifferentialSpeed(Payload, namedtuple('DifferentialSpeed_', 
    'left_speed right_speed left_accel right_accel')): 
    __slots__ =() 
    def __init__(self, **kwargs): 
     super(DifferentialSpeed, self).__init__(**kwargs) 
     # TODO: Field verification 
     print("foo") 

    @classmethod 
    def parse(self, raw): 
     # Dummy for now 
     return self(left_speed = 0.0, right_speed = 0.1, 
        left_accel = 0.2, right_accel = 0.3) 

    def __str__(self): 
     return "Left Speed: %fm/s\nRight Speed: %fm/s\n"\ 
      "Left Acceleration: %fm/s^2\nRight Acceleration: %fm/s^2" % (
      self.left_speed, self.right_speed, self.left_accel, self.right_accel) 


payload = DifferentialSpeed.parse('dummy') 
print(payload) 

Dies funktioniert, aber ich erhalte die folgende Warnung:

DeprecationWarning: object.__init__() takes no parameters 
    super(DifferentialSpeed, self).__init__(**kwargs) 

Wenn ich **kwargs aus dem Anruf zu entfernen, ist es noch zu funktionieren scheint, aber warum? Wie werden diese Argumente für den Konstruktor an das namedtuple übergeben? Ist dies garantiert, oder ein zufälliges Ergebnis davon, wie die mro etabliert wird?

Wenn ich von Super wegbleiben und es auf die alte Art machen wollte, gibt es eine Möglichkeit, auf das namedtuple zuzugreifen, um seinen Konstruktor aufzurufen? Ich würde es lieber nicht tun müssen:

DifferentialSpeed_ = namedtuple('DifferentialSpeed_', 
    'left_speed right_speed left_accel right_accel') 
class DifferentialSpeed(Payload, DifferentialSpeed_): 

Scheint irgendwie ausführlich und unnötig.

Was ist meine beste Vorgehensweise hier?

+1

Beachten Sie, wenn Sie versuchen, verwenden 'namedtuple' Speicher zu speichern, die Sie einstellen müssen' __slots__ =() 'in der abgeleiteten Klasse sowie die anderen erblichen' Payload' Klasse oder die Klasse wird immer noch ein "__dict__" haben. –

Antwort

26

Für den Anfang namedtuple(whatever) erbt von tuple, die unveränderlich ist, und unveränderliche Typen nicht die Mühe mit __init__, weil durch die Zeit __init__ das Objekt aufgerufen werden, ist bereits aufgebaut. Wenn Sie Argumente an die namedtuple Basisklasse übergeben möchten, müssen Sie stattdessen __new__ überschreiben.

Sie können die Definition des Ergebnisses von namedtuple() sehen, indem Sie ein verbose=true Argument übergeben; Ich finde es lehrreich.

+0

Oh, okay. Sehr interessant. Es ist mir egal, Dinge an das Nametuple zu übergeben; Ich wollte nur sicherstellen, dass es seine Argumente nicht verpassen wird. Vielleicht kann ich das ganze super() Problem einfach ignorieren. – mikepurvis

+0

In Ordnung, ich akzeptiere dieses, weil es hilfreich und kurz ist. Ich denke am Ende werde ich wahrscheinlich mit einem gewöhnlichen Konstruktor in Payload gehen, der eine polymorphe "self.verify()" aufruft, die dann Ausnahmen nach Bedarf auslösen kann. – mikepurvis

5

Sie haben drei Basisklassen: Payload, Ihr namedtuple DifferentialSpeed_ und die gemeinsame Basisklasse object. Keine der ersten zwei hat eine __init__ Funktion überhaupt, mit Ausnahme der von object geerbten. namedtuple benötigt keine __init__, da die Initialisierung der unveränderlichen Klassen durch __new__ erfolgt, die aufgerufen wird, bevor __init__ ausgeführt wird.

Seit super(DifferentialSpeed, self).__init__ löst in die nächste __init__ in der Aufrufkette, die nächste __init__ ist object.__init__, was bedeutet, sind Sie Argumente für diese Funktion übergeben. Es erwartet keine - es gibt keinen Grund, Argumente an object.__init__ zu übergeben.

(Früher zu akzeptieren und leise Argumente ignorieren Dieses Verhalten geht weg -. Es ist in Python gegangen. 3 - die, warum Sie einen DeprecationWarning bekommen ist)

Sie können das Problem deutlicher auslösen durch Hinzufügen eine Payload.__init__ Funktion, die keine Argumente annimmt. Wenn Sie versuchen, `* kwargs weiterzugeben, wird ein Fehler ausgelöst.

Die richtige Sache in diesem Fall zu tun ist fast sicher, das **kwargs Argument zu entfernen, und rufen Sie einfach super(DifferentialSpeed, self).__init__().Es braucht keine Argumente; DifferentialSpeed ist Payload seine eigenen Argumente übergeben, die Payload, und funktioniert weiter unten in der Rufkette, nichts darüber wissen.

+0

Ihre Aussage "Die richtige Sache in diesem Fall zu tun ist mit ziemlicher Sicherheit das' ** kwargs' Argument zu entfernen und einfach 'super (DifferentialSpeed, self) zu nennen .__ init __ (** kwargs)' "scheint widersprüchlich, sollte nicht der letzte Teil sei "Just Call' super (DifferentialSpeed, self) .__ init __() '"? – martineau

+0

@martineau: Natürlich, nur ein Tippfehler. –

+1

Falls jemand anderes hier ankommt, beachten Sie, dass ein anderer Grund dafür besteht, dass diese Warnung angezeigt wird oder nicht; Siehe den Kommentar zu '__new__' in Objects/typeobject.c. (Ich habe keine Lust, ins Detail zu gehen, da das OP diese Antwort vollständig ignoriert und eine angenommen hat, die seine Frage nicht einmal beantwortete ...) –

3

Wie andere darauf hingewiesen haben, sind Tupel ein unveränderlicher Typ, der in ihrer __new__() anstelle ihrer __init__() Methode initialisiert werden muss - also müssen Sie die erstere in Ihrer Unterklasse hinzufügen (und letztere loswerden). Im Folgenden wird erläutert, wie dies auf Ihren Beispielcode angewendet wird. Die einzige andere Änderung war das Hinzufügen einer from import...-Anweisung an den Anfang.

Hinweis:cls muss zweimal im super() Anruf in __new__() weitergegeben werden, da es sich um eine statische Methode ist, obwohl es spezielle Gefasste ist, so dass Sie es nicht erklären müssen, eins zu sein.

from collections import namedtuple 

class Payload: 
    def test(self): 
     print("bar") 

class DifferentialSpeed(Payload, namedtuple('DifferentialSpeed_', 
    'left_speed right_speed left_accel right_accel')): 
    #### NOTE: __new__ instead of an __init__ method #### 
    def __new__(cls, **kwargs): 
     self = super(DifferentialSpeed, cls).__new__(cls, **kwargs) 
     # TODO: Field verification 
     print("foo") 
     return self 

    @classmethod 
    def parse(self, raw): 
     # Dummy for now 
     return self(left_speed = 0.0, right_speed = 0.1, 
        left_accel = 0.2, right_accel = 0.3) 

    def __str__(self): 
     return "Left Speed: %fm/s\nRight Speed: %fm/s\n"\ 
      "Left Acceleration: %fm/s^2\nRight Acceleration: %fm/s^2" % (
      self.left_speed, self.right_speed, self.left_accel, self.right_accel) 


payload = DifferentialSpeed.parse('dummy') 
print(payload) 
+0

Es ist nicht nötig, '__new__' zu berühren. Er müsste das nur tun, wenn er die Argumente ändern möchte, mit denen 'DifferentialSpeed_' initialisiert wird; er überprüft sie nur. –

+0

@Glenn Maynard: Scheint mir wie '__new __()' * * * beteiligt zu sein, damit die Argumente verifiziert werden konnten * bevor * sie dem Tupel zugewiesen wurden (da sie später nicht geändert werden können). z.B. Sie könnten Standardwerte oder eine Ausnahme erhalten, ohne dass falsche Zuordnungen vorgenommen wurden. – martineau

+1

Eine Ausnahme von "__init__" zu erstellen funktioniert genauso gut. (Einen Standard zwangsweise zu erzwingen klingt nicht nach vernünftigem Verhalten.) –

Verwandte Themen