Commit c37e5e04 authored by Raymond Hettinger's avatar Raymond Hettinger

Add collections.NamedTuple

parent eb979889
......@@ -9,14 +9,16 @@
This module implements high-performance container datatypes. Currently,
there are two datatypes, deque and defaultdict.
there are two datatypes, deque and defaultdict, and one datatype factory
function, \function{NamedTuple}.
Future additions may include balanced trees and ordered dictionaries.
\versionchanged[Added defaultdict]{2.5}
\versionchanged[Added NamedTuple]{2.6}
\subsection{\class{deque} objects \label{deque-objects}}
\begin{funcdesc}{deque}{\optional{iterable}}
Returns a new deque objected initialized left-to-right (using
Returns a new deque object initialized left-to-right (using
\method{append()}) with data from \var{iterable}. If \var{iterable}
is not specified, the new deque is empty.
......@@ -339,3 +341,50 @@ Setting the \member{default_factory} to \class{set} makes the
>>> d.items()
[('blue', set([2, 4])), ('red', set([1, 3]))]
\end{verbatim}
\subsection{\function{NamedTuple} datatype factory function \label{named-tuple-factory}}
\begin{funcdesc}{NamedTuple}{typename, fieldnames}
Returns a new tuple subclass named \var{typename}. The new subclass is used
to create tuple-like objects that have fields accessable by attribute
lookup as well as being indexable and iterable. Instances of the subclass
also have a helpful docstring (with typename and fieldnames) and a helpful
\method{__repr__()} method which lists the tuple contents in a \code{name=value}
format.
\versionadded{2.6}
The \var{fieldnames} are specified in a single string and are separated by spaces.
Any valid Python identifier may be used for a field name.
Example:
\begin{verbatim}
>>> Point = NamedTuple('Point', 'x y')
>>> Point.__doc__ # docstring for the new datatype
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> 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)
\end{verbatim}
The use cases are the same as those for tuples. The named factories
assign meaning to each tuple position and allow for more readable,
self-documenting code. Can also be used to assign field names to tuples
returned by the \module{csv} or \module{sqlite3} modules. For example:
\begin{verbatim}
import csv
EmployeeRecord = NamedTuple('EmployeeRecord', 'name age title deparment paygrade')
for tup in csv.reader(open("employees.csv", "rb")):
print EmployeeRecord(*tup)
\end{verbatim}
\end{funcdesc}
__all__ = ['deque', 'defaultdict']
__all__ = ['deque', 'defaultdict', 'NamedTuple']
from _collections import deque, defaultdict
from operator import itemgetter as _itemgetter
import sys as _sys
def NamedTuple(typename, s):
"""Returns a new subclass of tuple with named fields.
>>> Point = NamedTuple('Point', 'x y')
>>> 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)
"""
field_names = s.split()
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)
repr_template = '%s(%s)' % (typename, ', '.join('%s=%%r' % name for name in field_names))
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 (so instances are same size as tuples)
__new__ = __new__,
__repr__ = lambda self, _format=repr_template.__mod__: _format(self),
__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__':
# verify that instances are pickable
from cPickle import loads, dumps
Point = NamedTuple('Point', 'x y')
p = Point(x=10, y=20)
assert p == loads(dumps(p))
import doctest
TestResults = NamedTuple('TestResults', 'failed attempted')
print TestResults(*doctest.testmod())
import unittest
from test import test_support
from collections import NamedTuple
class TestNamedTuple(unittest.TestCase):
def test_factory(self):
Point = NamedTuple('Point', 'x y')
self.assertEqual(Point.__name__, 'Point')
self.assertEqual(Point.__doc__, 'Point(x, y)')
self.assertEqual(Point.__slots__, ())
self.assertEqual(Point.__module__, __name__)
self.assertEqual(Point.__getitem__, tuple.__getitem__)
self.assert_('__getitem__' in Point.__dict__) # superclass methods localized
def test_instance(self):
Point = NamedTuple('Point', 'x y')
p = Point(11, 22)
self.assertEqual(p, Point(x=11, y=22))
self.assertEqual(p, Point(11, y=22))
self.assertEqual(p, Point(y=22, x=11))
self.assertEqual(p, Point(*(11, 22)))
self.assertEqual(p, Point(**dict(x=11, y=22)))
self.assertRaises(TypeError, Point, 1) # too few args
self.assertRaises(TypeError, Point, 1, 2, 3) # too many args
self.assertRaises(TypeError, eval, 'Point(XXX=1, y=2)', locals()) # wrong keyword argument
self.assertRaises(TypeError, eval, 'Point(x=1)', locals()) # missing keyword argument
self.assertEqual(repr(p), 'Point(x=11, y=22)')
self.assert_('__dict__' not in dir(p)) # verify instance has no dict
self.assert_('__weakref__' not in dir(p))
def test_tupleness(self):
Point = NamedTuple('Point', 'x y')
p = Point(11, 22)
self.assert_(isinstance(p, tuple))
self.assertEqual(p, (11, 22)) # matches a real tuple
self.assertEqual(tuple(p), (11, 22)) # coercable to a real tuple
self.assertEqual(list(p), [11, 22]) # coercable to a list
self.assertEqual(max(p), 22) # iterable
self.assertEqual(max(*p), 22) # star-able
x, y = p
self.assertEqual(p, (x, y)) # unpacks like a tuple
self.assertEqual((p[0], p[1]), (11, 22)) # indexable like a tuple
self.assertRaises(IndexError, p.__getitem__, 3)
self.assertEqual(p.x, x)
self.assertEqual(p.y, y)
self.assertRaises(AttributeError, eval, 'p.z', locals())
def test_main(verbose=None):
test_classes = [TestNamedTuple]
test_support.run_unittest(*test_classes)
if __name__ == "__main__":
test_main(verbose=True)
......@@ -138,6 +138,8 @@ Library
- Added heapq.merge() for merging sorted input streams.
- Added collections.NamedTuple() for assigning field names to tuples.
- Added itertools.izip_longest().
- Have the encoding package's search function dynamically import using absolute
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment