[Python-Dev] Py2.6 ideas

Michele Simionato michele.simionato at gmail.com
Tue Feb 20 06:25:24 CET 2007


Raymond Hettinger <raymond.hettinger <at> verizon.net> writes:
> * Add a pure python named_tuple class to the collections module.  I've been 
> using the class for about a year and found that it greatly improves the 
> usability of tuples as records. 
> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/500261

The implementation of this recipe is really clean and I like it a lot
(I even think of including it in our codebase), but there are a few issues
that I would like to point out.

1. I find the camelcase confusing and I think that NamedTuple should be 
   spelled namedtuple, since is a function, not a class. The fact that it 
   returns classes does not count ;)

2. I agree with Giovanni Bajo, the constructor signature should be consistent
   with regular tuples. For instance I want to be able to map a named tuple 
   over a record set as returned by fetchall.

3. I would like to pass the list of the fields as a sequence, not as
   a string. It would be more consistent and it would make easier
   the programmatic creation of NamedTuple classes at runtime.

4. I want help(MyNamedTuple) to work well; in particular it should
   display the right module name. That means
   that in the m dictionary you should add a __module__ attribute:
   
    __module__ = sys._getframe(1).f_globals['__name__']

5. The major issue is that pickle does work with named tuples since the
   __module__ attribute is wrong. The suggestion in #4 would solve even
   this issue for free.

6. The ability to pass a show function to the __repr__ feems over-engineering
   to me.

In short, here is how I would change the recipe:

import sys
from operator import itemgetter

def namedtuple(f):
    """Returns a new subclass of tuple with named fields.

    >>> Point = namedtuple('Point x y'.split())
    >>> Point.__doc__           # docstring for the new class
    'Point(x, y)'
    >>> p = Point((11,), y=22)  # instantiate with positional args or keywords
    >>> p[0] + p[1]             # works just like the tuple (11, 22)
    33
    >>> x, y = p                # unpacks just like a tuple
    >>> x, y
    (11, 22)
    >>> p.x + p.y               # fields also accessable by name
    33
    >>> p                       # readable __repr__ with name=value style 
    Point(x=11, y=22)

    """
    typename, field_names = f[0], f[1:]
    nargs = len(field_names)

    def __new__(cls, args=(), **kwds):
        if kwds:
            try:
                args += tuple(kwds[name] for name in field_names[len(args):])
            except KeyError, name:
                raise TypeError(
                    '%s missing required argument: %s' % (typename, name))
        if len(args) != nargs:
            raise TypeError(
                '%s takes exactly %d arguments (%d given)' %
                (typename, nargs, len(args)))
        return tuple.__new__(cls, args)

    template = '%s(%s)' % (
        typename, ', '.join('%s=%%r' % name for name in field_names))
    
    def __repr__(self):
        return template % self

    m = dict(vars(tuple)) # pre-lookup superclass methods (for faster lookup)
    m.update(__doc__= '%s(%s)' % (typename, ', '.join(field_names)),
             __slots__ = (),    # no per-instance dict 
             __new__ = __new__,
             __repr__ = __repr__,
             __module__ = sys._getframe(1).f_globals['__name__'],
             )
    m.update((name, property(itemgetter(index)))
             for index, name in enumerate(field_names))

    return type(typename, (tuple,), m)


if __name__ == '__main__':
    import doctest
    TestResults = namedtuple(['TestResults', 'failed', 'attempted'])
    print TestResults(doctest.testmod())




More information about the Python-Dev mailing list