Welcome, guest | Sign In | My Account | Store | Cart

Fast lightweight attribute style access to list. I think this is closest to mutable C struct type.

Same as collections.namedtuple but subclasses list, so fields may be modified. Same as popular Record class but smaller and faster. http://code.activestate.com/recipes/502237-simple-record-aka-struct-type/

Python, 64 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def namedlist(typename, field_names):
    """Returns a new subclass of list with named fields.

    >>> Point = namedlist('Point', ('x', 'y'))
    >>> Point.__doc__                   # docstring for the new class
    'Point(x, y)'
    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
    >>> p[0] + p[1]                     # indexable like a plain list
    33
    >>> x, y = p                        # unpack like a regular list
    >>> x, y
    (11, 22)
    >>> p.x + p.y                       # fields also accessable by name
    33
    >>> d = p._asdict()                 # convert to a dictionary
    >>> d['x']
    11
    >>> Point(**d)                      # convert from a dictionary
    Point(x=11, y=22)
    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
    Point(x=100, y=22)
    """
    fields_len = len(field_names)
    fields_text = repr(tuple(field_names)).replace("'", "")[1:-1] # tuple repr without parens or quotes

    class ResultType(list):
        __slots__ = ()
        _fields = field_names

        def _fixed_length_error(*args, **kwargs):
            raise TypeError(u"Named list has fixed length")
        append = _fixed_length_error
        insert = _fixed_length_error
        pop = _fixed_length_error
        remove = _fixed_length_error

        def sort(self):
            raise TypeError(u"Sorting named list in place would corrupt field accessors. Use sorted(x)")

        def _replace(self, **kwargs):
            values = map(kwargs.pop, field_names, self)
            if kwargs:
                raise TypeError(u"Unexpected field names: {s!r}".format(kwargs.keys()))

            if len(values) != fields_len:
                raise TypeError(u"Expected {e} arguments, got {n}".format(
                    e=fields_len, n=len(values)))

            return ResultType(*values)

        def __repr__(self):
            items_repr=", ".join("{name}={value!r}".format(name=name, value=value)
                                 for name, value in zip(field_names, self))
            return "{typename}({items})".format(typename=typename, items=items_repr)

    ResultType.__init__ = eval("lambda self, {fields}: self.__setitem__(slice(None, None, None), [{fields}])".format(fields=fields_text))
    ResultType.__name__ = typename

    for i, name in enumerate(field_names):
        fget = eval("lambda self: self[{0:d}]".format(i))
        fset = eval("lambda self, value: self.__setitem__({0:d}, value)".format(i))
        setattr(ResultType, name, property(fget, fset))

    return ResultType

Stupid benchmarks follow, as always you should not trust them. In the end namedtuple wins in create/modify speed at the cost of increased memory usage and read speed.

If you need to copy.deepcopy namedlist, remove overridden append method.

Takes more memory than namedtuple:

>>> sys.getsizeof(namedtuple('S', tuple("abcde"))(*"abcde"))
96
>>> sys.getsizeof(namedlist('S', tuple("abcde"))(*"abcde"))
136

Creates faster than namedtuple:

In [45]: %timeit namedtuple('S', tuple(s))(*s) 1000 loops, best of 3: 586 us per loop

In [44]: %timeit namedlist('S', tuple(s))(*s) 1000 loops, best of 3: 465 us per loop

Field read access is more than twice as slow:

In [54]: %timeit s1.a 10000000 loops, best of 3: 191 ns per loop

In [55]: %timeit s2.a 1000000 loops, best of 3: 420 ns per loop

Write access (warning, unfair comparison) is expectedly much faster because for namedtuple you have to recreate whole tuple:

In [63]: %timeit s1._replace(a="new") 100000 loops, best of 3: 2.1 us per loop

In [64]: %timeit s2.a = "new" 1000000 loops, best of 3: 561 ns per loop

Namedtuple wins fair _replace comparison:

In [65]: %timeit s1._replace(a="new") 100000 loops, best of 3: 2.08 us per loop

In [66]: %timeit s2._replace(a="new") 100000 loops, best of 3: 3.44 us per loop

Created by Sergey Shepelev on Thu, 16 Feb 2012 (MIT)
Python recipes (4591)
Sergey Shepelev's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks