mirror of
https://github.com/python/cpython.git
synced 2025-11-25 21:11:09 +00:00
Add collections.NamedTuple
This commit is contained in:
parent
eb9798892d
commit
c37e5e04eb
4 changed files with 170 additions and 3 deletions
|
|
@ -9,14 +9,16 @@
|
||||||
|
|
||||||
|
|
||||||
This module implements high-performance container datatypes. Currently,
|
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.
|
Future additions may include balanced trees and ordered dictionaries.
|
||||||
\versionchanged[Added defaultdict]{2.5}
|
\versionchanged[Added defaultdict]{2.5}
|
||||||
|
\versionchanged[Added NamedTuple]{2.6}
|
||||||
|
|
||||||
\subsection{\class{deque} objects \label{deque-objects}}
|
\subsection{\class{deque} objects \label{deque-objects}}
|
||||||
|
|
||||||
\begin{funcdesc}{deque}{\optional{iterable}}
|
\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}
|
\method{append()}) with data from \var{iterable}. If \var{iterable}
|
||||||
is not specified, the new deque is empty.
|
is not specified, the new deque is empty.
|
||||||
|
|
||||||
|
|
@ -339,3 +341,50 @@ Setting the \member{default_factory} to \class{set} makes the
|
||||||
>>> d.items()
|
>>> d.items()
|
||||||
[('blue', set([2, 4])), ('red', set([1, 3]))]
|
[('blue', set([2, 4])), ('red', set([1, 3]))]
|
||||||
\end{verbatim}
|
\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}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,62 @@
|
||||||
__all__ = ['deque', 'defaultdict']
|
__all__ = ['deque', 'defaultdict', 'NamedTuple']
|
||||||
|
|
||||||
from _collections import deque, defaultdict
|
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())
|
||||||
|
|
|
||||||
57
Lib/test/test_collections.py
Normal file
57
Lib/test/test_collections.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
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 heapq.merge() for merging sorted input streams.
|
||||||
|
|
||||||
|
- Added collections.NamedTuple() for assigning field names to tuples.
|
||||||
|
|
||||||
- Added itertools.izip_longest().
|
- Added itertools.izip_longest().
|
||||||
|
|
||||||
- Have the encoding package's search function dynamically import using absolute
|
- Have the encoding package's search function dynamically import using absolute
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue