NamedTupleMetaclass is a metaclass for creating tuples with named elements that can be accessed by index and by name.
NamedTuple is a class factory for NamedTupleMetaclass instances.
This is an improved version of recipe #303439
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 65 66 67 68 69 70 71 72 73 74 75 76 | class NamedTupleMetaclass(type):
"""Metaclass for a tuple with elements named and indexed.
NamedTupleMetaclass instances must set the 'names' class attribute
with a list of strings of valid identifiers, being the names for the
elements. The elements can then be obtained by looking up the name or the index.
"""
def __init__(cls, classname, bases, classdict):
super(NamedTupleMetaclass, cls).__init__(cls, classname, bases, classdict)
# Must derive from tuple
if not tuple in bases:
raise ValueError, "'%s' must derive from tuple type." % classname
# Create a dictionary to keep track of name->index correspondence
cls._nameindices = dict(zip(classdict['names'], range(len(classdict['names']))))
def instance_getattr(self, name):
"""Look up a named element."""
try:
return self[self.__class__._nameindices[name]]
except KeyError:
raise AttributeError, "object has no attribute named '%s'" % name
cls.__getattr__ = instance_getattr
def instance_setattr(self, name, value):
raise TypeError, "'%s' object has only read-only attributes (assign to .%s)" % (self.__class__.__name__, name)
cls.__setattr__ = instance_setattr
def instance_new(cls, seq_or_dict):
"""Accept either a sequence of values or a dict as parameters."""
if isinstance(seq_or_dict, dict):
seq = []
for name in cls.names:
try:
seq.append(seq_or_dict[name])
except KeyError:
raise KeyError, "'%s' element of '%s' not given" % (name, cls.__name__)
else:
seq = seq_or_dict
return tuple.__new__(cls, seq)
cls.__new__ = staticmethod(instance_new)
def NamedTuple(*namelist):
"""Class factory function for creating named tuples."""
class _NamedTuple(tuple):
__metaclass__ = NamedTupleMetaclass
names = list(namelist)
return _NamedTuple
# Example follows
if __name__ == "__main__":
class PersonTuple(tuple):
__metaclass__ = NamedTupleMetaclass
names = ["name", "age", "height"]
person1 = PersonTuple(["James", 26, 185])
person2 = PersonTuple(["Sarah", 24, 170])
person3 = PersonTuple(dict(name="Tony", age=53, height=192))
print person1
for i, name in enumerate(PersonTuple.names):
print name, ":", person2[i]
print "%s is %s years old and %s cm tall." % person3
person3.name = "this will fail"
|
A common usage of tuples is to represent aggregations of heterogenous data, as a kind of anonymous data structure. The built-in tuple type only allows the elements to be accessed by their indices; this leads to a loss of clarity: seeing x[3] in code is less clear than x.middlename, for example.
Classes using NamedTupleMetaclass are tuples with named elements. These elements can then be accessed by index or by name, as convenient. See the code for some example usage.
Because NamedTuple is a subclass of tuple, all the standard tuple methods will work on it as usual. This provides the convenience of tuples with the clarity of named elements.
Not enough examples. The examples of usage should have included using the factory function, and accessing the named members:
Great speed improvement. It struck me that, since the members of the tuple are immutable, the best implementation would just keep another reference to them for each attribute. This not only ends up being simpler, but from my calculations about 1400% faster -- a very significant increase:
More prior art. http://just.letterror.com/ltrwiki/JustVanRossum_2fNamedTuple
Even more prior art. http://www.sil-tec.gr/~tzot/python/ (TupleStruct.py)
The improvement for speed I have used was to set property gets for the members using the operator.attrgetter function. I didn't set separately members since that would mean extra memory usage.
Oldest article referencing this module from Dec 27, 2002, in a thread where Alex Martelli was involved too:
http://groups.google.com/groups?selm=735o0vk4uciifl692o51fk9om87o37bo6k%404ax.com