Welcome, guest | Sign In | My Account | Store | Cart
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"

History