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/
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