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