13

Quindi, ho un gran numero di messaggi di classi di payload per un'API seriale, ognuno dei quali ha un numero di campi immutabili, un metodo di analisi e alcuni metodi che sono condivisi. Il modo in cui sto strutturando questo è che ognuno erediterà da un namedtuple per i comportamenti del campo e riceverà i metodi comuni da una classe genitore. Tuttavia, sto avendo qualche difficoltà con i costruttori:In Python, come posso chiamare la super-classe quando si tratta di una namedtuple una tantum?

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) 

Questo funziona, ma ottengo il seguente avviso:

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

Se rimuovo **kwargs dalla chiamata, sembra ancora funzionare, ma perché? In che modo questi argomenti vengono passati al costruttore per essere passati alla namedtuple? È garantito questo o un risultato casuale di come viene stabilito il mro?

Se volevo stare lontano da Super, e farlo alla vecchia maniera, c'è un modo in cui posso accedere a namedtuple per chiamare il suo costruttore? Preferirei non doverlo fare:

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

Sembra un po 'prolisso e inutile.

Qual è la mia migliore linea d'azione qui?

+1

Si noti che se si sta cercando di usare 'namedtuple' per risparmiare memoria, è necessario impostare' __slots__ =() 'nella classe derivata, così come l'altro ereditato classe' Payload', o la classe avrà ancora un '__dict__'. –

risposta

26

Per cominciare, namedtuple(whatever) eredita da tuple, che è immutabile, e tipi immutabili non si preoccupano con __init__, perché per il momento __init__ è chiamato l'oggetto è già costruito. Se si desidera passare argomenti alla classe base namedtuple, è necessario sostituire invece __new__.

È possibile visualizzare la definizione del risultato di namedtuple() passando un argomento verbose=true; Lo trovo educativo.

+0

Oh, ok. Molto interessante. Non mi importa del passare le cose alla namedtuple; Volevo solo assicurarmi che non avrebbe perso le sue argomentazioni. Forse posso semplicemente ignorare l'intero super() problema del tutto. – mikepurvis

+0

Va bene, accetterò questo, per essere utile e breve. Penso che alla fine avrò probabilmente un costruttore comune in Payload, che chiama un "auto.verify()" polimorfo, che può quindi generare eccezioni se necessario. – mikepurvis

5

Sono disponibili tre classi di base: Payload, la namedtuple DifferentialSpeed_ e la classe di base comune object. Nessuno dei primi due ha una funzione __init__, tranne quella ereditata da object. namedtuple non ha bisogno di un __init__, poiché l'inizializzazione delle classi immutabili viene eseguita da __new__, che viene richiamato prima dell'esecuzione di __init__.

Dal super(DifferentialSpeed, self).__init__ risolve alla prossima __init__ nella catena di chiamate, la prossima __init__ è object.__init__, il che significa che si sta passando argomenti per quella funzione. Non si aspetta nulla - non c'è motivo di passare argomenti a object.__init__.

(ha usato per accettare e silenziosamente ignorare gli argomenti che il comportamento è andando via -. È andato in Python. 3 - che è il motivo per cui si ottiene un DeprecationWarning)

è possibile attivare il problema in modo più chiaro con l'aggiunta di una funzione Payload.__init__ che non accetta argomenti. Quando provi a passare insieme a `* kwargs, si genera un errore.

La cosa corretta da fare in questo caso è quasi certamente rimuovere l'argomento **kwargs e chiamare semplicemente super(DifferentialSpeed, self).__init__().Non prende argomenti; DifferentialSpeed sta passando Payloadproprio argomenti che Payload e funzioni più in basso nella catena di chiamata, non sapere nulla.

+0

La tua affermazione "La cosa giusta da fare in questo caso è quasi certamente rimuovere l'argomento' ** kwargs', e chiamare semplicemente 'super (DifferentialSpeed, self) .__ init __ (** kwargs)' "sembra contraddittorio, non dovrebbe l'ultima parte è "chiama semplicemente' super (DifferentialSpeed, self) .__ init __() '"? – martineau

+0

@martineau: Certo, solo un refuso. –

+1

Nel caso in cui qualcun altro arrivi qui, nota che c'è un'altra ragione implicata in questo avviso che compare o meno; vedere il commento su '__new__' in Objects/typeobject.c. (Non ho voglia di entrare nei dettagli, dal momento che l'OP ha ignorato completamente questa risposta e ne ha accettato uno che non ha nemmeno risposto alla sua domanda ...) –

3

Come altri hanno sottolineato, le tuple sono un tipo immutabile, che deve essere inizializzato nel loro __new__() invece del loro metodo __init__() - quindi è necessario aggiungere il primo nella sottoclasse (e sbarazzarsi di quest'ultimo). Di seguito è riportato come questo verrà applicato al codice di esempio. L'unica altra modifica era l'aggiunta di una dichiarazione from import... all'inizio.

Nota:cls deve essere passato due volte nel super() chiamata in __new__() perché è un metodo statico anche se è speciale con carter in modo da non dover dichiarare di essere uno.

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

Non è necessario toccare '__new__'. Avrebbe dovuto farlo solo se voleva modificare gli argomenti con cui 'DifferentialSpeed_' è stato inizializzato; li sta solo verificando. –

+0

@Glenn Maynard: Mi sembra come '__new __()' * dovrebbe * essere coinvolto in modo che gli argomenti possano essere verificati * prima * fossero assegnati alla tupla (poiché non possono essere modificati in seguito). per esempio. Potrebbero essere dati valori predefiniti o un'eccezione sollevata senza assegnazioni fittizie. – martineau

+1

Aumentare un'eccezione da '__init__' funziona altrettanto bene. (Forzare forzatamente un valore predefinito non sembra un comportamento corretto.) –