I think of a tuple with a given sequence of names for its fields as a
type (a subclass of tuple, sure). For example, the name->index
correspondence for all the pseudotuples-with-9-items returned by module
time is just the same one -- why would I want to carry around that
dictionary for each INSTANCE of a time-pseudotuple, rather than having
it once and for all in the type?
You prefer to specify the names every time you make an instance and
build and carry the needed name->index dict along with each instance.
Ah well, I guess that's OK, but I don't really see the advantage
compared to the custom-metaclass approach.
Ah. This is one reason why a non-metaclass version is not so good.
There really is no advantage to my inheritance-based implementation --
I tried to make a metaclass version but ran into some issues. However,
on my second try I succeeded -- see below.
Great, thanks -- I should get the snapshot tomorrow (or whenever they do
start working again over in Vancouver, since I get it from
ActiveState
and I'll be happy to consider presenting your approach
(and a custom metaclass for contrast;-).
Below is a better (=easier to use) implementation using metaclasses;
I've submitted it to the Cookbook anyway (recipe #303481) despite
being past the deadling. The NamedTuple function is for convenience
(although the example doesn't use it for the sake of explicitness).
NamedTuples accepts a single argument: a sequence -- as for tuple() --
or a dictionary with (at least) the names that the NamedTuple expects.
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
print "%s is %s years old and %s cm tall." % person3
person3.name = "this will fail"