Recipe 6.7. Implementing Tuples with Named Items
Credit: Gonçalo Rodrigues, Raymond
Hettinger
Problem
Python tuples are handy ways to group pieces of information, but
having to access each item by numeric index is a bother.
You'd like to build tuples whose items are also
accessible as named attributes.
Solution
A factory function is the simplest way to generate the required
subclass of tuple:
# use operator.itemgetter if we're in 2.4, roll our own if we're in 2.3
try:
from operator import itemgetter
except ImportError:
def itemgetter(i):
def getter(self): return self[i]
return getter
def superTuple(typename, *attribute_names):
" create and return a subclass of `tuple', with named attributes "
# make the subclass with appropriate _ _new_ _ and _ _repr_ _ specials
nargs = len(attribute_names)
class supertup(tuple):
_ _slots_ _ = ( ) # save memory, we don't need per-instance dict
def _ _new_ _(cls, *args):
if len(args) != nargs:
raise TypeError, '%s takes exactly %d arguments (%d given)' % (
typename, nargs, len(args))
return tuple._ _new_ _(cls, args)
def _ _repr_ _(self):
return '%s(%s)' % (typename, ', '.join(map(repr, self)))
# add a few key touches to our new subclass of `tuple'
for index, attr_name in enumerate(attribute_names):
setattr(supertup, attr_name, property(itemgetter(index)))
supertup._ _name_ _ = typename
return supertup
Discussion
You often want to pass data around by means of tuples, which play the
role of C's structs, or that of
simple records in other languages. Having to remember which numeric
index corresponds to which field, and accessing the fields by
indexing, is often bothersome. Some Python Standard Library modules,
such as time and os, which in
old Python versions used to return tuples, have fixed the problem by
returning, instead, instances of tuple-like types that let you access
the fields by name, as attributes, as well as by index, as items.
This recipe shows you how to get the same effect for your code,
essentially by automatically building a custom subclass of
tuple.Orchestrating the building of a new, customized type can be achieved
in several ways; custom metaclasses are often the best approach for
such tasks. In this case, however, a simple factory function is quite
sufficient, and you should never use more power than you need. Here
is how you can use this recipe's
superTuple factory function in your code, assuming
you have saved this recipe's Solution as a module
named supertuple.py somewhere along your Python
sys.path:
>>> import supertuple
>>> Point = supertuple.superTuple('Point', 'x', 'y')
>>> Point
<class 'supertuple.Point'>
>>> p = Point(1, 2, 3) # wrong number of fields
Traceback (most recent call last):
File ", line 1, in ?
File "C:\Python24\Lib\site-packages\superTuple.py",
line 16, in _ _new_ _
raise TypeError, '%s takes exactly %d arguments (%d given)' % (
TypeError: Point takes exactly 2 arguments (3 given)
>>> p = Point(1, 2) # let's do it right this time
>>> p
Point(1, 2)
>>> print p.x, p.y
1 2
Function
superTuple's implementation is
quite straightforward. To build the new subclass,
superTuple uses a class
statement, and in that statement's body, it defines
three specials: an "empty"
_ _slots_ _ (just to save memory, since our
supertuple instances don't need any per-instance
dictionary anyway); a _ _new_ _ method that checks
the number of arguments before delegating to tuple._ _new_
_; and an appropriate _ _repr_ _ method.
After the new class object is built, we set into it a
property for each named attribute we want. Each
such property has only a
"getter", since our supertuples,
just like tuples themselves, are immutableno setting of
fields. Finally, we set the new class' name and
return the class object.
Each of the
getters is easily built by a simple call to the built-in
itemgetter from the standard library module
operator. Since
operator.itemgetter was introduced in Python 2.4,
at the very start of our module we ensure we have a suitable
itemgetter at hand anyway, even in Python 2.3, by
rolling our own if necessary.
See Also
Library Reference and Python in a
Nutshell docs for property, _
_slots_ _, tuple, and special methods
_ _new_ _ and _ _repr_ _;
(Python 2.4 only) module
operator's function
itemgetter.