Tuple question

Andrew Durdin adurdin at gmail.com
Sun Sep 5 05:39:05 EDT 2004


On Sat, 4 Sep 2004 15:58:08 +0200, Alex Martelli <aleaxit at yahoo.com> wrote:
> 
> 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?
<snip>
> 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[i]
    print "%s is %s years old and %s cm tall." % person3

    person3.name = "this will fail"



More information about the Python-list mailing list