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

This recipe provides a simple and efficient way of creating records (a.k.a. structs) where each argument to the constructor becomes an attribute of the object.

Python, 59 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
class InitFromSlots(type):
    def __new__(meta, name, bases, bodydict):
        slots = bodydict['__slots__']
        if slots and '__init__' not in bodydict:
            parts = ['def __init__(self, %s):' % ', '.join(slots)]
            for slot in slots:
                parts.append('    self.%s = %s' % (slot, slot))
            exec '\n'.join(parts) in bodydict
        super_new =  super(InitFromSlots, meta).__new__
        return super_new(meta, name, bases, bodydict)

class Record(object):
    __metaclass__ = InitFromSlots
    __slots__ = ()
    def _items(self):
        for name in self.__slots__:
            yield name, getattr(self, name)
    def __repr__(self):
        args = ', '.join('%s=%r' % tup for tup in self._items())
        return '%s(%s)' % (type(self).__name__, args)
    def __iter__(self):
        for name in self.__slots__:
            yield getattr(self, name)
    def __getstate__(self):
        return dict(self._items())
    def __setstate__(self, statedict):
        self.__init__(**statedict)

# =========================
# At the interactive prompt
# =========================

>>> class Point(Record):
...     __slots__ = 'x', 'y'
... 
>>> Point(3, 4)
Point(x=3, y=4)
>>> Point(y=5, x=2)
Point(x=2, y=5)
>>> point = Point(-1, 42)
>>> point.x, point.y
(-1, 42)
>>> x, y = point
>>> x, y
(-1, 42)

>>> class Badger(Record):
...     __slots__ = 'large', 'wooden'
...     
>>> badger = Badger('spam', 'eggs')
>>> import pickle
>>> pickle.loads(pickle.dumps(badger))
Badger(large='spam', wooden='eggs')

>>> class Answer(Record):
...     __slots__ = 'life', 'universe', 'everything'
...     
>>> eval(repr(Answer(42, 42, 42)))
Answer(life=42, universe=42, everything=42)

The NamedTuple recipe (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/500261) offers a hybrid of tuples and simple record-like objects. Sometimes, however, you only need attribute-style access, not indexing. This recipe caters to such use cases.

For object creation and attribute access, this recipe will generally be faster than recipe 500261 -- __init__ is a simple sequence of assignments, and attributes are handled through fast C-level slots. For unpacking and iteration, this recipe will be slower than recipe 500261 -- __iter__ is a Python-level generator instead of C-level tuple iteration.

Update 21 Feb 07: Record now supplies __iter__ to allow for tuple unpacking. InitFromSlots now only adds __init__ if it is not defined.

Created by Steven Bethard on Tue, 20 Feb 2007 (PSF)
Python recipes (4591)
Steven Bethard's recipes (7)

Required Modules

  • (none specified)

Other Information and Tasks