web-dev-qa-db-fra.com

Pourquoi Python ne prend-il pas en charge le type d'enregistrement? (C'est-à-dire mutable namedtuple)

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.

47
Salil

Python <3,3

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.

Mise à jour Python ≥3.3

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).

48
tzot

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']
18
Cameron

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'
>>> 
10
Abbafei

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)
9
Leif Bork

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).

4
intellimath

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?

1
Ali

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.

0
BobC

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})
0
Architektor

Vous pourriez faire quelque chose comme ça dictsous-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
0
martineau