Il semble que ceci ou ceci sont des fils liés, mais que nous n’avons toujours pas compris les choses :)
J'essaie de créer une sous-classe de namedtuple
et de fournir différents initialiseurs afin de pouvoir construire des objets de différentes manières. Par exemple:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __init__(self, obj) : # Initialize a C instance by copying values from obj
... self.x = obj.a
... self.y = obj.b
... def __init__(self, x, y) : # Initialize a C instance from the parameters
... self.x = x
... self.y = y
Cependant, cela ne fonctionne pas:
>>> c = C(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
AttributeError: can't set attribute
Après quelques recherches (par exemple, voir this thread), j’ai essayé d’utiliser des constructeurs au lieu d’initialiseurs:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __new__(cls, obj) :
... self = super(C, cls).__new__(cls, obj.a, obj.b)
... def __new__(cls, x, y) :
... self = super(C, cls).__new__(cls, x, y)
qui semblait construire un objet mais je ne peux pas lire ses attributs:
>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'
Où est-ce que je vais mal ici? Comment créer une sous-classe avec plusieurs constructeurs ou initialiseurs?
Les n-uplets nommés sont immuables, vous ne pouvez donc pas les manipuler dans l'initialiseur __init__
. Votre seule option est de remplacer la méthode __new__
:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, obj):
return super(C, cls).__new__(cls, obj.x, obj.y)
Notez que, puisque __new__
est une méthode par défaut pour les nouvelles instances, vous devez renvoyer la nouvelle instance créée. Si vous n'utilisez pas return
dans la méthode __new__
, la valeur de retour par défaut est None
, ce qui vous indique votre erreur.
Démo avec un objet avec les attributs x
et y
:
>>> class C(namedtuple('C', 'x, y')):
... __slots__ = ()
... def __new__(cls, obj):
... return super(C, cls).__new__(cls, obj.x, obj.y)
...
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)
Python ne prend pas en charge la surcharge de méthode; généralement, vous utilisez des arguments de mot-clé facultatifs ou des méthodes de classe supplémentaires en tant que méthodes d'usine.
Le module datetime
, par exemple, propose plusieurs méthodes d'usine permettant de créer des objets ne correspondant pas au constructeur standard. datetime.datetime.fromtimestamp()
crée une instance datetime.datetime
à partir d'une seule valeur numérique, de même que datetime.datetime.fromordinal()
; sauf qu'ils interprètent le nombre de différentes manières.
Si vous voulez supporter les arguments variables, faites:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, x, y=None):
if y is None:
# assume attributes
x, y = x.x, x.y
return super(C, cls).__new__(cls, x, y)
Ici, y
est un argument optionnel, défini par défaut sur None
s'il n'est pas fourni par l'appelant:
>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
L’alternative, utilisant une méthode de classe, serait:
class C(namedtuple('C', 'x, y')):
@classmethod
def from_attributes(cls, obj):
return cls(obj.x, obj.y)
Il existe maintenant deux méthodes d’usine; un défaut et un nommé:
>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
Deux choses: premièrement, vous ne tirez pas vraiment grand profit de nomtuple ici, pour autant que je sache. Alors peut-être devriez-vous simplement passer à une classe normale. En outre, vous ne pouvez pas surcharger le
Deuxièmement, d'autres possibilités qui pourraient vous aider avec votre problème:
Modèle de conception d'usine - au lieu de mettre les différents paramètres dans le constructeur, utilisez une classe qui prend différents types de paramètres et appelle le constructeur avec des arguments appropriés, en dehors de l'objet . recordtype - un mutable namedtuple, qui autorise les valeurs par défaut, mais vous permet également d'écrire votre sous-classe comme vous le souhaitiez initialement . bunch - pas exactement un Tuple nommé, mais vous permet de créer des objets quelque peu arbitraires.
Il existe une solution de contournement pour modifier l'attribut d'un nom nommé.
import collections
def updateTuple(NamedTuple,nameOfNamedTuple):
## Convert namedtuple to an ordered dictionary, which can be updated
NamedTuple_asdict = NamedTuple._asdict()
## Make changes to the required named attributes
NamedTuple_asdict['path']= 'www.google.com'
## reconstruct the namedtuple using the updated ordered dictionary
updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)
return updated_NamedTuple
Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")
Je vous suggère d'utiliser la méthode _replace
from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)