Thanks for the comments. Will experiment with them and get back to you..
The alternate naming is fine. At work, we call it ntuple().
The constructor signature has been experimented with several time and had best results in its current form which allows the *args for casting a record set returned by SQL or by the CSV module as in Point(*fetchall(s)), and it allows for direct construction with Point(2,3) without the slower and weirder form: Point((2,3)). Also, the current signature works better with keyword arguments: Point(x=2, y=3) or Point(2, y=3) which wouldn't be common but would be consistent with the relationship between keyword arguments and positional arguments in other parts of the language.
The string form for the named tuple factory was arrived at because it was easier to write, read, and alter than its original form with a list of strings: Contract = namedtuple('Contract stock strike volatility expiration rate iscall') vs. Contract = namedtuple('Contract', 'stock', 'strike', 'volatility', 'expiration', 'rate', 'iscall') That former is easier to edit and to re-arrange. Either form is trivial to convert programmatically to the other and the definition step only occurs once while the use of the new type can appear many times throughout the code. Having experimented with both forms, I've found the string form to be best thought it seems a bit odd. Yet, the decision isn't central to the proposal and is still an open question.
The __module__ idea is nice. Will add it. Likewise with pickling.
The 'show' part of __repr__ was done for speed (localized access versus accessing the enclosing scope. Will rename it to _show to make it clear that it is not part of the API.
Thanks for the accolades and the suggestions.
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.
- 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 ;)
- 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.
- 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.
- 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__']
- 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.
- The ability to pass a show function to the __repr__ feems over-engineering
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
p = Point((11,), y=22) # instantiate with positional args or keywords p + p # works just like the tuple (11, 22)
x, y = p # unpacks just like a tuple x, y
p.x + p.y # fields also accessable by name
p # readable __repr__ with name=value style
""" typename, field_names = f, 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())
Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/python%40rcn.com