Why does Python not support record type? (i.e. mutable namedtuple)

Question:

Why does Python not support a record type natively? It’s a matter of having a mutable version of namedtuple.

I could use namedtuple._replace. But I need to have these records in a collection and since namedtuple._replace creates another instance, I also need to modify the collection which becomes messy quickly.

Background:
I have a device whose attributes I need to get by polling it over TCP/IP. i.e. its representation is a mutable object.

Edit:
I have a set of devices for whom I need to poll.

Edit:
I need to iterate through the object displaying its attributes using PyQt. I know I can add special methods like __getitem__ and __iter__, but I want to know if there is an easier way.

Edit:
I would prefer a type whose attribute are fixed (just like they are in my device), but are mutable.

Asked By: Salil

||

Answers:

Is there any reason you can’t use a regular dictionary? It seems like the attributes don’t have a specific ordering in your particular situation.

Alternatively, you could also use a class instance (which has nice attribute access syntax). You could use __slots__ if you wish to avoid having a __dict__ created for each instance.

I’ve also just found a recipe for “records”, which are described as mutable named-tuples. They are implemented using classes.

Update:

Since you say order is important for your scenario (and you want to iterate through all the attributes) an OrderedDict seems to be the way to go. This is part of the standard collections module as of Python 2.7; there are other implementations floating around the internet for Python < 2.7.

To add attribute-style access, you can subclass it like so:

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)

Then you can do:

>>> 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']
Answered By: Cameron

This can be done using an empty class and instances of it, like this:

>>> class a(): pass
... 
>>> ainstance = a()
>>> ainstance.b = 'We want Moshiach Now'
>>> ainstance.b
'We want Moshiach Now'
>>> 
Answered By: Abbafei

Python <3.3

You mean something like this?

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)

Note that the methods provided are just a sample of possible methods.

Python ≥3.3 update

You can use types.SimpleNamespace:

>>> import types
>>> r= types.SimpleNamespace()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14

dir(r) would provide you with the attribute names (filtering out all .startswith("__"), of course).

Answered By: tzot

There’s a library similar to namedtuple, but mutable, called recordtype.

Package home: http://pypi.python.org/pypi/recordtype

Simple example:

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)

Simple default value example:

Basis = recordtype('Basis', [('x', 1), ('y', 0)])

Iterate through the fields of person1 in order:

map(person1.__getattribute__, Person._fields)
Answered By: Leif Bork

You could do something like thisdictsubclass which is its own __dict__. The basic concept is the same as that of the ActiveState AttrDict recipe, but the implementation is simpler. The result is something more mutable than you need since both an instance’s attributes and their values are changeable. Although the attributes aren’t ordered, you can iterate through the current ones and/or their values.

class Record(dict):
    def __init__(self, *args, **kwargs):
        super(Record, self).__init__(*args, **kwargs)
        self.__dict__ = self
Answered By: martineau

Based on several useful tricks gathered over time, this “frozenclass” decorator does pretty much everything needed: http://pastebin.com/fsuVyM45

Since that code is over 70% documentation and tests, I won’t say more here.

Answered By: BobC

This answer duplicates another one.
There is a mutable alternative to collections.namedtuplerecordclass.

It has same API and minimal memory footprint (actually it also faster). It support assignments. For example:

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)

There is more complete example (it also include performance comparisons).

Answered By: intellimath

Here is a complete mutable namedtuple I made, which behaves like a list and is totally compatible with it.

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})
Answered By: Architektor

In the closely related Existence of mutable named tuple in Python? question 13 tests are used for comparing 6 mutable alternatives to namedtuple.

The latest namedlist 1.7 passes all of these tests with both Python 2.7 and Python 3.5 as of Jan 11, 2016. It is a pure python implementation.

The second best candidate according to these tests is the recordclass which is a C extension. Of course, it depends on your requirements whether a C extension is preferred or not.

For further details, especially for the tests, see Existence of mutable named tuple in Python?

Answered By: Ali

This question is old, but just for the sake of completeness, Python 3.7 has dataclasses which are pretty much records.

>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class MyRecord:
...     name: str
...     age: int = -1
...
>>> rec = MyRecord('me')
>>> rec.age = 127
>>> print(rec)
MyRecord(name='me', age=127)

The attrs third party library provides more functionality for both Python 2 and Python 3. Nothing wrong with vendoring dependencies either if the requirement is more around things you can’t keep locally rather than specifically only using the stdlib. dephell has a nice helper for doing that.

Answered By: Daniel

As tzot stated, since Python ≥3.3, Python does have a mutable version of namedtuple: types.SimpleNamespace.

These things are very similar to the new C# 9 Records.

Here are some usage examples:

Positional constructor arguments

>>> import types
>>>
>>> class Location(types.SimpleNamespace):
...   def __init__(self, lat=0, long=0):
...     super().__init__(lat=lat, long=long)
...
>>> loc_1 = Location(49.4, 8.7)

Pretty repr

>>> loc_1
Location(lat=49.4, long=8.7)

Mutable

>>> loc_2 = Location()
>>> loc_2
Location(lat=0, long=0)
>>> loc_2.lat = 49.4
>>> loc_2
Location(lat=49.4, long=0)

Value semantics for equality

>>> loc_2 == loc_1
False
>>> loc_2.long = 8.7
>>> loc_2 == loc_1
True

Can add attributes at runtime

>>> loc_2.city = 'Heidelberg'
>>> loc_2
Answered By: Grilse
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.