En Python, comment dois-je appeler la classe super quand il est un one-off namedtuple?
-
28-09-2019 - |
Question
Alors, j'ai un grand nombre de classes de charge de message pour une API série, dont chacun a un certain nombre de champs immuables, une méthode d'analyse, et certaines méthodes qui sont partagées. La façon dont je suis la structuration est que chaque hériteront d'un namedtuple pour les comportements sur le terrain, et recevoir les méthodes communes d'une classe parente. Cependant, je vais avoir quelques difficultés avec les constructeurs:
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)
Cela fonctionne, mais je reçois l'avertissement suivant:
DeprecationWarning: object.__init__() takes no parameters
super(DifferentialSpeed, self).__init__(**kwargs)
Si je retire **kwargs
de l'appel, il semble encore au travail, mais pourquoi? Comment ces arguments au constructeur se passent par les namedtuple? Est-ce garanti, ou un résultat aléatoire de la façon dont le MRO soit établie?
Si je voulais rester loin de super et de le faire à l'ancienne, est-il un moyen que je peux accéder au namedtuple d'appeler son constructeur? Je préfère ne pas avoir à faire ceci:
DifferentialSpeed_ = namedtuple('DifferentialSpeed_',
'left_speed right_speed left_accel right_accel')
class DifferentialSpeed(Payload, DifferentialSpeed_):
Semble sorte de verbeux et inutile.
Quel est mon meilleur plan d'action ici?
La solution
Pour commencer, hérite de namedtuple(whatever)
de tuple
, qui est immuable, et les types immuables ne se soucient pas __init__
, parce que par le __init__
de temps est appelé l'objet est déjà construit. Si vous voulez passer des arguments à la classe de base de namedtuple
vous devrez passer outre __new__
à la place.
Vous pouvez voir la définition du résultat de namedtuple()
en passant dans un argument verbose=true
; Je trouve l'éducation.
Autres conseils
Vous avez trois classes de base: Payload
, votre namedtuple DifferentialSpeed_
, et la classe de base commune object
. Aucun des deux premiers ont une fonction de __init__
du tout, à l'exception de l'une héritée de object
. namedtuple
n'a pas besoin d'__init__
, depuis l'initialisation des classes immuables se fait par __new__
, qui est appelée avant __init__
est exécuté.
Depuis résorbe super(DifferentialSpeed, self).__init__
à la prochaine __init__
dans la chaîne d'appel, la prochaine __init__
est object.__init__
, ce qui signifie que vous passez des arguments à cette fonction. Il ne prévoit pas -. Il n'y a aucune raison d'être des arguments à passer object.__init__
(Il permet d'accepter et ignorer en silence les arguments que le comportement va loin -. Il est parti en Python 3 -. Qui est la raison pour laquelle vous obtenez un DeprecationWarning)
Vous pouvez déclencher le problème plus clairement en ajoutant une fonction de Payload.__init__
qui ne prend aucun argument. Lorsque vous essayez de transmettre `* kwargs , il soulèverai une erreur.
La bonne chose à faire dans ce cas est presque certainement d'enlever l'argument **kwargs
, et il suffit d'appeler super(DifferentialSpeed, self).__init__()
. Il ne prend pas d'arguments; DifferentialSpeed
passe Payload
son propres arguments Payload
et fonctions plus bas dans la chaîne d'appel, ne savent rien.
Comme d'autres l'ont souligné-out, sont un type tuples immuable, qui doit être initialisé dans leur __new__()
au lieu de leur méthode de __init__()
- donc vous devez ajouter l'ancien dans votre sous-classe (et se débarrasser de ce dernier). Ci-dessous, comment cela sera appliqué à votre code d'exemple. Le seul autre changement a été l'ajout d'une déclaration de from import...
au début.
Remarque: cls
doit être passé deux fois dans l'appel super()
à __new__()
car il est une méthode statique bien qu'il soit si vous spécial tubé ne devez pas déclarer être un
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)