13

Por lo tanto, tengo un gran número de clases de carga útil de mensaje para una API en serie, cada una de las cuales tiene varios campos inmutables, un método de análisis sintáctico y algunos métodos que se comparten. La forma en que estoy estructurando esto es que cada uno heredará de un namedtuple los comportamientos de campo y recibirá los métodos comunes de una clase principal. Sin embargo, estoy teniendo algunas dificultades con los constructores:En Python, ¿cómo llamo a la súper clase cuando se trata de una sola vez?

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) 

Esto funciona, pero me da la siguiente advertencia:

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

Si quito **kwargs de la llamada, todavía parece funcionar, ¿pero por qué? ¿Cómo se transmiten esos argumentos al constructor al namedtuple? ¿Esto está garantizado, o es un resultado aleatorio de cómo se establece el mro?

Si quisiera mantenerme alejado de super, y hacerlo de la manera antigua, ¿hay alguna forma de que pueda acceder a la llamada namedtuple para llamar a su constructor? Prefiero no tener que hacer esto:

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

Parece tipo de detallado e innecesario.

¿Cuál es mi mejor línea de conducta aquí?

+1

Tenga en cuenta que si usted está tratando de usar 'namedtuple' para ahorrar memoria, es necesario establecer' __slots__ =() 'en la clase derivada, así como la otra heredada clase' Payload', o de la clase todavía tendrá un '__dict__'. –

Respuesta

26

Para empezar, namedtuple(whatever) hereda de tuple, que es inmutable, y tipos inmutables no se moleste con __init__, porque en el momento __init__ se llama el objeto ya está construido. Si desea pasar argumentos a la clase base namedtuple, tendrá que anular __new__.

Puede ver la definición del resultado de namedtuple() pasando un argumento verbose=true; Lo encuentro educativo

+0

Oh, está bien. Muy interesante. No me importa pasar cosas a la tilde nombrada; Solo quería asegurarme de que no iba a perderse sus argumentos. Quizás puedo simplemente ignorar el problema completo de super() por completo. – mikepurvis

+0

Está bien, voy a aceptar este, por ser útil y breve. Creo que al final, probablemente vaya con un constructor común en Payload, que llama a un "self.verify() autodestructivo polimórfico", que luego puede lanzar excepciones según sea necesario. – mikepurvis

5

Tiene tres clases base: Payload, su namedtuple DifferentialSpeed_, y la clase base común object. Ninguno de los dos primeros tiene una función __init__, excepto la heredada de object. namedtuple no necesita un __init__, ya que la inicialización de clases inmutables se realiza por __new__, que se llama antes de ejecutar __init__.

Desde super(DifferentialSpeed, self).__init__ resuelve a la siguiente __init__ en la cadena de llamadas, el siguiente __init__ es object.__init__, lo que significa que está pasando argumentos para esa función. No espera ninguno; no hay motivo para pasar argumentos al object.__init__.

(Se utiliza para aceptar y silenciosamente ignorar los argumentos de que el comportamiento se va -. Que se ha ido en Python 3 -. Por lo que se obtiene una DeprecationWarning)

Usted puede desencadenar el problema con mayor claridad mediante la adición una función Payload.__init__ que no toma argumentos. Cuando intente pasar `` kwargs, se producirá un error.

Lo correcto en este caso es eliminar el argumento **kwargs, y simplemente llamar al super(DifferentialSpeed, self).__init__().No toma ningún argumento; DifferentialSpeed está pasando Payload es propios argumentos que Payload, y funciona más abajo en la cadena de llamadas, no se sabe nada.

+0

Su declaración "Lo correcto en este caso es casi con seguridad eliminar el argumento' ** kwargs', y simplemente llamar a 'super (DifferentialSpeed, self) .__ init __ (** kwargs)' "parece contradictorio, no debería la última parte es "simplemente llame a' super (DifferentialSpeed, self) .__ init __() '"? – martineau

+0

@martineau: Por supuesto, solo un error tipográfico. –

+1

En caso de que alguien más llegue aquí, tenga en cuenta que hay otra razón involucrada en esta advertencia que aparece o no; vea el comentario sobre '__new__' en Objects/typeobject.c. (No tengo ganas de entrar en detalles, ya que el OP ignoró por completo esta respuesta y aceptó una que ni siquiera respondió su pregunta ...) –

3

Como otros han señalado de salida, tuplas son un tipo inmutable, que debe ser inicializado en su __new__() en lugar de su método __init__() - Así que hay que añadir la antigua en la subclase (y deshacerse de este último). A continuación se muestra cómo se aplicaría a su código de ejemplo. El único otro cambio fue agregar una declaración from import... al comienzo.

Nota:cls tiene que pasar dos veces en el super() llamada en __new__() porque es un método estático aunque es especial con carcasa por lo que no tiene que declarar que es 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

No es necesario tocar '__new__'. Solo necesitaría hacer eso si quisiera modificar los argumentos con los que 'DifferentialSpeed_' se inicializa; él solo los está verificando. –

+0

@Glenn Maynard: Me parece que '__new__()' * * tendría que estar involucrado para que los argumentos se puedan verificar * antes * se asignaron a la tupla (ya que no se pueden cambiar más adelante). p.ej. Se les podría dar valores predeterminados o una excepción planteada sin realizar ninguna asignación falsa. – martineau

+1

La generación de una excepción de '__init__' también funciona. (Forzar silenciosamente un valor predeterminado no suena como un comportamiento sensato). –

Cuestiones relacionadas