Pourquoi Python ne supporte-t-il pas nativement un type d'enregistrement? Il s'agit d'avoir une version mutable de namedtuple.
Je pourrais utiliser namedtuple._replace
. Mais je dois avoir ces enregistrements dans une collection et depuis namedtuple._replace
crée une autre instance, j'ai également besoin de modifier la collection qui devient rapidement désordonnée.
Contexte: J'ai un appareil dont je dois obtenir les attributs en l'interrogeant sur TCP/IP. c'est-à-dire que sa représentation est un objet mutable.
Edit: J'ai un ensemble d'appareils pour lesquels je dois interroger.
Edit: J'ai besoin d'itérer à travers l'objet affichant ses attributs en utilisant PyQt. Je sais que je peux ajouter des méthodes spéciales comme __getitem__
et __iter__
, mais je veux savoir s'il existe un moyen plus simple.
Edit: je préférerais un type dont l'attribut est fixe (tout comme ils sont dans mon appareil), mais sont mutables.
Tu veux dire quelque chose comme ca?
class Record(object):
__slots__= "attribute1", "attribute2", "attribute3",
def items(self):
"dict style items"
return [
(field_name, getattr(self, field_name))
for field_name in self.__slots__]
def __iter__(self):
"iterate over fields Tuple/list style"
for field_name in self.__slots__:
yield getattr(self, field_name)
def __getitem__(self, index):
"Tuple/list style getitem"
return getattr(self, self.__slots__[index])
>>> r= Record()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14
>>> print r.items()
[('attribute1', 'hello'), ('attribute2', 'there'), ('attribute3', 3.1400000000000001)]
>>> print Tuple(r)
('hello', 'there', 3.1400000000000001)
Notez que les méthodes fournies ne sont qu'un échantillon des méthodes possibles.
Vous pouvez utiliser types.SimpleNamespace
:
>>> import types
>>> r= types.SimpleNamespace()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14
dir(r)
vous fournirait les noms d'attributs (filtrant tout .startswith("__")
, bien sûr).
Y a-t-il une raison pour laquelle vous ne pouvez pas utiliser un dictionnaire ordinaire? Il semble que les attributs n'aient pas d'ordre spécifique dans votre situation particulière.
Alternativement, vous pouvez également utiliser une instance de classe (qui a la syntaxe d'accès aux attributs Nice). Vous pouvez utiliser __slots__
si vous souhaitez éviter d'avoir un __dict__
créé pour chaque instance.
Je viens également de trouver un recette pour "records" , qui sont décrits comme des tuples nommés mutables. Ils sont implémentés à l'aide de classes.
Mise à jour:
Puisque vous dites que l'ordre est important pour votre scénario (et que vous voulez parcourir tous les attributs), un OrderedDict
semble être la voie à suivre. Cela fait partie du module standard collections
à partir de Python 2.7; il existe d'autres implémentations flottant sur Internet pour Python <2.7.
Pour ajouter un accès de style attribut, vous pouvez le sous-classer comme suit:
from collections import OrderedDict
class MutableNamedTuple(OrderedDict):
def __init__(self, *args, **kwargs):
super(MutableNamedTuple, self).__init__(*args, **kwargs)
self._initialized = True
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
if hasattr(self, '_initialized'):
super(MutableNamedTuple, self).__setitem__(name, value)
else:
super(MutableNamedTuple, self).__setattr__(name, value)
Ensuite, vous pouvez faire:
>>> t = MutableNamedTuple()
>>> t.foo = u'Crazy camels!'
>>> t.bar = u'Yay, attribute access'
>>> t.foo
u'Crazy camels!'
>>> t.values()
[u'Crazy camels!', u'Yay, attribute access']
Cela peut être fait en utilisant une classe vide et des instances de celle-ci, comme ceci:
>>> class a(): pass
...
>>> ainstance = a()
>>> ainstance.b = 'We want Moshiach Now'
>>> ainstance.b
'We want Moshiach Now'
>>>
Il existe une bibliothèque similaire à namedtuple, mais modifiable, appelée recordtype.
Accueil du package: http://pypi.python.org/pypi/recordtype
Exemple simple:
from recordtype import recordtype
Person = recordtype('Person', 'first_name last_name phone_number')
person1 = Person('Trent', 'Steele', '637-3049')
person1.last_name = 'Terrence';
print person1
# Person(first_name=Trent, last_name=Terrence, phone_number=637-3049)
Exemple de valeur par défaut simple:
Basis = recordtype('Basis', [('x', 1), ('y', 0)])
Parcourez les champs de person1
en ordre:
map(person1.__getattribute__, Person._fields)
Cette réponse fait double emploi ne autre . Il existe une alternative mutable à collections.namedtuple
- classe d'enregistrement .
Il a la même API et la même empreinte mémoire que namedtuple
(en fait, il est également plus rapide). Il prend en charge les affectations. Par exemple:
from recordclass import recordclass
Point = recordclass('Point', 'x y')
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
Il y a plus complet exemple (il inclut également des comparaisons de performances).
Dans le Existence de mutable nommé Tuple in Python? , les tests de la question 13 sont utilisés pour comparer 6 alternatives mutables à namedtuple
.
Le dernier namedlist 1.7 passe tous ces tests avec les deux Python 2.7 et Python 3,5 au 11 janvier 2016. Il s'agit d'une implémentation pure python.
Le deuxième meilleur candidat selon ces tests est le recordclass
qui est une extension C. Bien sûr, cela dépend de vos besoins, qu'une extension C soit préférée ou non.
Pour plus de détails, en particulier pour les tests, voir Existence de mutable nommé Tuple en Python?
Sur la base de plusieurs astuces utiles recueillies au fil du temps, ce décorateur "surgelé" fait à peu près tout le nécessaire: http://Pastebin.com/fsuVyM45
Étant donné que ce code contient plus de 70% de documentation et de tests, je n'en dirai pas plus ici.
Voici un tuple nommé mutable complet que j'ai fait, qui se comporte comme une liste et est totalement compatible avec elle.
class AbstractNamedArray():
"""a mutable collections.namedtuple"""
def __new__(cls, *args, **kwargs):
inst = object.__new__(cls) # to rename the class
inst._list = len(cls._fields)*[None]
inst._mapping = {}
for i, field in enumerate(cls._fields):
inst._mapping[field] = i
return inst
def __init__(self, *args, **kwargs):
if len(kwargs) == 0 and len(args) != 0:
assert len(args) == len(self._fields), 'bad number of arguments'
self._list = list(args)
Elif len(args) == 0 and len(kwargs) != 0:
for field, value in kwargs.items():
assert field in self._fields, 'field {} doesn\'t exist'
self._list[self._mapping[field]] = value
else:
raise ValueError("you can't mix args and kwargs")
def __getattr__(self, x):
return object.__getattribute__(self, '_list')[object.__getattribute__(self, '_mapping')[x]]
def __setattr__(self, x, y):
if x in self._fields:
self._list[self._mapping[x]] = y
else:
object.__setattr__(self, x, y)
def __repr__(self):
fields = []
for field, value in Zip(self._fields, map(self.__getattr__, self._fields)):
fields.append('{}={}'.format(field, repr(value)))
return '{}({})'.format(self._name, ', '.join(fields))
def __iter__(self):
yield from self._list
def __list__(self):
return self._list[:]
def __len__(self):
return len(self._fields)
def __getitem__(self, x):
return self._list[x]
def __setitem__(self, x, y):
self._list[x] = y
def __contains__(self, x):
return x in self._list
def reverse(self):
self._list.reverse()
def copy(self):
return self._list.copy()
def namedarray(name, fields):
"""used to construct a named array (fixed-length list with named fields)"""
return type(name, (AbstractNamedarray,), {'_name': name, '_fields': fields})
Vous pourriez faire quelque chose comme ça dict
sous-classe qui est son propre __dict__
. Le concept de base est le même que celui de la recette ActiveState AttrDict , mais l'implémentation est plus simple. Le résultat est quelque chose de plus modifiable que ce dont vous avez besoin car les attributs d'une instance et leurs valeurs sont modifiables. Bien que les attributs ne soient pas classés, vous pouvez parcourir les attributs actuels et/ou leurs valeurs.
class Record(dict):
def __init__(self, *args, **kwargs):
super(Record, self).__init__(*args, **kwargs)
self.__dict__ = self