Commit 5a41daf0 authored by Raymond Hettinger's avatar Raymond Hettinger

Improvements to NamedTuple's implementation, tests, and documentation

parent 6290305e
...@@ -378,14 +378,25 @@ Point(x=11, y=22) ...@@ -378,14 +378,25 @@ Point(x=11, y=22)
The use cases are the same as those for tuples. The named factories The use cases are the same as those for tuples. The named factories
assign meaning to each tuple position and allow for more readable, assign meaning to each tuple position and allow for more readable,
self-documenting code. Named tuples can also be used to assign field names self-documenting code. Named tuples can also be used to assign field names
to tuples to tuples returned by the \module{csv} or \module{sqlite3} modules.
returned by the \module{csv} or \module{sqlite3} modules. For example: For example:
\begin{verbatim} \begin{verbatim}
from itertools import starmap
import csv import csv
EmployeeRecord = NamedTuple('EmployeeRecord', 'name age title department paygrade') EmployeeRecord = NamedTuple('EmployeeRecord', 'name age title department paygrade')
for tup in csv.reader(open("employees.csv", "rb")): for record in starmap(EmployeeRecord, csv.reader(open("employees.csv", "rb"))):
print EmployeeRecord(*tup) print record
\end{verbatim}
To cast an individual record stored as \class{list}, \class{tuple}, or some other
iterable type, use the star-operator to unpack the values:
\begin{verbatim}
>>> Color = NamedTuple('Color', 'name code')
>>> m = dict(red=1, green=2, blue=3)
>>> print Color(*m.popitem())
Color(name='blue', code=3)
\end{verbatim} \end{verbatim}
\end{funcdesc} \end{funcdesc}
...@@ -24,30 +24,29 @@ def NamedTuple(typename, s): ...@@ -24,30 +24,29 @@ def NamedTuple(typename, s):
""" """
field_names = s.split() field_names = s.split()
nargs = len(field_names) assert ''.join(field_names).replace('_', '').isalpha() # protect against exec attacks
argtxt = ', '.join(field_names)
def __new__(cls, *args, **kwds): reprtxt = ', '.join('%s=%%r' % name for name in field_names)
if kwds: template = '''class %(typename)s(tuple):
try: '%(typename)s(%(argtxt)s)'
args += tuple(kwds[name] for name in field_names[len(args):]) __slots__ = ()
except KeyError, name: def __new__(cls, %(argtxt)s):
raise TypeError('%s missing required argument: %s' % (typename, name)) return tuple.__new__(cls, (%(argtxt)s,))
if len(args) != nargs: def __repr__(self):
raise TypeError('%s takes exactly %d arguments (%d given)' % (typename, nargs, len(args))) return '%(typename)s(%(reprtxt)s)' %% self
return tuple.__new__(cls, args) ''' % locals()
for i, name in enumerate(field_names):
repr_template = '%s(%s)' % (typename, ', '.join('%s=%%r' % name for name in field_names)) template += '\t%s = property(itemgetter(%d))\n' % (name, i)
m = dict(itemgetter=_itemgetter)
m = dict(vars(tuple)) # pre-lookup superclass methods (for faster lookup) exec template in m
m.update(__doc__= '%s(%s)' % (typename, ', '.join(field_names)), result = m[typename]
__slots__ = (), # no per-instance dict (so instances are same size as tuples) if hasattr(_sys, '_getframe'):
__new__ = __new__, result.__module__ = _sys._getframe(1).f_globals['__name__']
__repr__ = lambda self, _format=repr_template.__mod__: _format(self), return result
__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__': if __name__ == '__main__':
......
...@@ -11,7 +11,6 @@ class TestNamedTuple(unittest.TestCase): ...@@ -11,7 +11,6 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Point.__slots__, ()) self.assertEqual(Point.__slots__, ())
self.assertEqual(Point.__module__, __name__) self.assertEqual(Point.__module__, __name__)
self.assertEqual(Point.__getitem__, tuple.__getitem__) self.assertEqual(Point.__getitem__, tuple.__getitem__)
self.assert_('__getitem__' in Point.__dict__) # superclass methods localized
def test_instance(self): def test_instance(self):
Point = NamedTuple('Point', 'x y') Point = NamedTuple('Point', 'x y')
...@@ -50,8 +49,10 @@ class TestNamedTuple(unittest.TestCase): ...@@ -50,8 +49,10 @@ class TestNamedTuple(unittest.TestCase):
def test_main(verbose=None): def test_main(verbose=None):
import collections as CollectionsModule
test_classes = [TestNamedTuple] test_classes = [TestNamedTuple]
test_support.run_unittest(*test_classes) test_support.run_unittest(*test_classes)
test_support.run_doctest(CollectionsModule, verbose)
if __name__ == "__main__": if __name__ == "__main__":
test_main(verbose=True) test_main(verbose=True)
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