Em Python, como chamo a superclasse quando é uma instância nomeada única?
-
28-09-2019 - |
Pergunta
Portanto, tenho um grande número de classes de carga útil de mensagem para uma API serial, cada uma delas com vários campos imutáveis, um método de análise e alguns métodos que são compartilhados.A maneira como estou estruturando isso é que cada um herdará de uma tupla nomeada para os comportamentos do campo e receberá os métodos comuns de uma classe pai.Porém, estou tendo algumas dificuldades com os construtores:
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)
Isso funciona, mas recebo o seguinte aviso:
DeprecationWarning: object.__init__() takes no parameters
super(DifferentialSpeed, self).__init__(**kwargs)
Se eu remover **kwargs
da ligação, ainda parece funcionar, mas por quê?Como esses argumentos do construtor são passados para o nomeadotuple?Isso é garantido ou é um resultado aleatório de como o mro se estabelece?
Se eu quisesse ficar longe do super e fazer isso da maneira antiga, existe alguma maneira de acessar o nomeadotuple para chamar seu construtor?Prefiro não ter que fazer isso:
DifferentialSpeed_ = namedtuple('DifferentialSpeed_',
'left_speed right_speed left_accel right_accel')
class DifferentialSpeed(Payload, DifferentialSpeed_):
Parece meio prolixo e desnecessário.
Qual é o meu melhor curso de ação aqui?
Solução
Para iniciantes, namedtuple(whatever)
herda de tuple
, que é imutável, e tipos imutáveis não se preocupam com __init__
, porque na hora __init__
é chamado o objeto já está construído.Se você quiser passar argumentos para o namedtuple
classe base você terá que substituir __new__
em vez de.
Você pode ver a definição do resultado de namedtuple()
passando em um verbose=true
argumento;Acho isso educativo.
Outras dicas
Você tem três classes base: Payload
, seu nomeadotuple DifferentialSpeed_
, e a classe base comum object
.Nenhum dos dois primeiros tem __init__
função, exceto aquela herdada de object
. namedtuple
não precisa de um __init__
, já que a inicialização de classes imutáveis é feita por __new__
, que é chamado antes __init__
é executado.
Desde super(DifferentialSpeed, self).__init__
resolve para o próximo __init__
na cadeia de chamadas, o próximo __init__
é object.__init__
, o que significa que você está passando argumentos para essa função.Ele não espera nada - não há razão para passar argumentos para object.__init__
.
(Costumava aceitar e ignorar argumentos silenciosamente.Esse comportamento está desaparecendo - desapareceu no Python 3 - e é por isso que você recebe um DeprecationWarning.)
Você pode desencadear o problema de forma mais clara adicionando um Payload.__init__
função que não aceita argumentos.Quando você tenta passar adiante `*kwargs, isso gerará um erro.
A coisa correta a fazer neste caso é quase certamente remover o **kwargs
argumento, e apenas ligue super(DifferentialSpeed, self).__init__()
.Não são necessários argumentos; DifferentialSpeed
está passando Payload
isso é ter argumentos que Payload
, e funções mais abaixo na cadeia de chamadas, não sabemos nada sobre.
Como outros apontaram, as tuplas são um tipo imutável, que deve ser inicializado em seu __new__()
em vez de seus __init__()
método - então você precisa adicionar o primeiro em sua subclasse (e se livrar do último).Abaixo está como isso seria aplicado ao seu código de exemplo.A única outra mudança foi adicionar um from import...
declaração para o início.
Observação: cls
tem que ser passado duas vezes no super()
ligue __new__()
porque é um método estático, embora tenha um caso especial, então você não precisa declará-lo como um.
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)