diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index a7d9ab6f610..ca8a096b52c 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -12,7 +12,7 @@ This module implements high-performance container datatypes. Currently, there are two datatypes, :class:`deque` and :class:`defaultdict`, and -one datatype factory function, :func:`NamedTuple`. Python already +one datatype factory function, :func:`named_tuple`. Python already includes built-in containers, :class:`dict`, :class:`list`, :class:`set`, and :class:`tuple`. In addition, the optional :mod:`bsddb` module has a :meth:`bsddb.btopen` method that can be used to create in-memory @@ -25,7 +25,7 @@ ordered dictionaries. Added :class:`defaultdict`. .. versionchanged:: 2.6 - Added :class:`NamedTuple`. + Added :func:`named_tuple`. .. _deque-objects: @@ -348,14 +348,14 @@ Setting the :attr:`default_factory` to :class:`set` makes the .. _named-tuple-factory: -:func:`NamedTuple` Factory Function for Tuples with Named Fields ----------------------------------------------------------------- +:func:`named_tuple` Factory Function for Tuples with Named Fields +----------------------------------------------------------------- Named tuples assign meaning to each position in a tuple and allow for more readable, self-documenting code. They can be used wherever regular tuples are used, and they add the ability to access fields by name instead of position index. -.. function:: NamedTuple(typename, fieldnames, [verbose]) +.. function:: named_tuple(typename, fieldnames, [verbose]) Returns a new tuple subclass named *typename*. The new subclass is used to create tuple-like objects that have fields accessable by attribute lookup as @@ -363,22 +363,22 @@ they add the ability to access fields by name instead of position index. helpful docstring (with typename and fieldnames) and a helpful :meth:`__repr__` method which lists the tuple contents in a ``name=value`` format. - The *fieldnames* are a single string with each fieldname separated by a space - and/or comma (for example "x y" or "x, y"). Alternately, the *fieldnames* - can be specified as list or tuple of strings. Any valid Python identifier - may be used for a fieldname except for names starting and ending with double - underscores. + The *fieldnames* are a single string with each fieldname separated by whitespace + and/or commas (for example 'x y' or 'x, y'). Alternatively, the *fieldnames* + can be specified as a list of strings (such as ['x', 'y']). Any valid + Python identifier may be used for a fieldname except for names starting and + ending with double underscores. If *verbose* is true, will print the class definition. - *NamedTuple* instances do not have per-instance dictionaries, so they are + Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples. .. versionadded:: 2.6 Example:: - >>> Point = NamedTuple('Point', 'x y', verbose=True) + >>> Point = named_tuple('Point', 'x y', verbose=True) class Point(tuple): 'Point(x, y)' __slots__ = () @@ -410,27 +410,35 @@ Example:: Named tuples are especially useful for assigning field names to result tuples returned by the :mod:`csv` or :mod:`sqlite3` modules:: + EmployeeRecord = named_tuple('EmployeeRecord', 'name, age, title, department, paygrade') + from itertools import starmap import csv - EmployeeRecord = NamedTuple('EmployeeRecord', 'name age title department paygrade') for emp in starmap(EmployeeRecord, csv.reader(open("employees.csv", "rb"))): print emp.name, emp.title -When casting a single record to a *NamedTuple*, use the star-operator [#]_ to unpack + import sqlite3 + conn = sqlite3.connect('/companydata') + cursor = conn.cursor() + cursor.execute('SELECT name, age, title, department, paygrade FROM employees') + for emp in starmap(EmployeeRecord, cursor.fetchall()): + print emp.name, emp.title + +When casting a single record to a named tuple, use the star-operator [#]_ to unpack the values:: >>> t = [11, 22] >>> Point(*t) # the star-operator unpacks any iterable object Point(x=11, y=22) -When casting a dictionary to a *NamedTuple*, use the double-star-operator:: +When casting a dictionary to a named tuple, use the double-star-operator:: >>> d = {'x': 11, 'y': 22} >>> Point(**d) Point(x=11, y=22) In addition to the methods inherited from tuples, named tuples support -additonal methods and a read-only attribute. +two additonal methods and a read-only attribute. .. method:: somenamedtuple.__asdict__() @@ -464,8 +472,8 @@ additonal methods and a read-only attribute. >>> p.__fields__ # view the field names ('x', 'y') - >>> Color = NamedTuple('Color', 'red green blue') - >>> Pixel = NamedTuple('Pixel', Point.__fields__ + Color.__fields__) + >>> Color = named_tuple('Color', 'red green blue') + >>> Pixel = named_tuple('Pixel', Point.__fields__ + Color.__fields__) >>> Pixel(11, 22, 128, 255, 0) Pixel(x=11, y=22, red=128, green=255, blue=0)' diff --git a/Lib/collections.py b/Lib/collections.py index 6a285539aa0..242de60564d 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -1,13 +1,13 @@ -__all__ = ['deque', 'defaultdict', 'NamedTuple'] +__all__ = ['deque', 'defaultdict', 'named_tuple'] from _collections import deque, defaultdict from operator import itemgetter as _itemgetter import sys as _sys -def NamedTuple(typename, field_names, verbose=False): +def named_tuple(typename, field_names, verbose=False): """Returns a new subclass of tuple with named fields. - >>> Point = NamedTuple('Point', 'x y') + >>> Point = named_tuple('Point', 'x y') >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords @@ -36,6 +36,10 @@ def NamedTuple(typename, field_names, verbose=False): raise ValueError('Type names and field names can only contain alphanumeric characters and underscores') if any(name.startswith('__') and name.endswith('__') for name in field_names): raise ValueError('Field names cannot start and end with double underscores') + if any(name[:1].isdigit() for name in field_names): + raise ValueError('Field names cannot start with a number') + if len(field_names) != len(set(field_names)): + raise ValueError('Encountered duplicate field name') # Create and fill-in the class template argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes @@ -83,10 +87,10 @@ def NamedTuple(typename, field_names, verbose=False): if __name__ == '__main__': # verify that instances can be pickled from cPickle import loads, dumps - Point = NamedTuple('Point', 'x, y', True) + Point = named_tuple('Point', 'x, y', True) p = Point(x=10, y=20) assert p == loads(dumps(p)) import doctest - TestResults = NamedTuple('TestResults', 'failed attempted') + TestResults = named_tuple('TestResults', 'failed attempted') print TestResults(*doctest.testmod()) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index c260bc78fff..1af20cadb94 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1,23 +1,25 @@ import unittest from test import test_support -from collections import NamedTuple +from collections import named_tuple class TestNamedTuple(unittest.TestCase): def test_factory(self): - Point = NamedTuple('Point', 'x y') + Point = named_tuple('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.assertRaises(ValueError, NamedTuple, 'abc%', 'def ghi') - self.assertRaises(ValueError, NamedTuple, 'abc', 'def g%hi') - self.assertRaises(ValueError, NamedTuple, 'abc', '__def__ ghi') - NamedTuple('Point0', 'x1 y2') # Verify that numbers are allowed in names + self.assertRaises(ValueError, named_tuple, 'abc%', 'def ghi') + self.assertRaises(ValueError, named_tuple, 'abc', 'def g%hi') + self.assertRaises(ValueError, named_tuple, 'abc', '__def__ ghi') + self.assertRaises(ValueError, named_tuple, 'abc', 'def def ghi') + self.assertRaises(ValueError, named_tuple, 'abc', '8def 9ghi') + named_tuple('Point0', 'x1 y2') # Verify that numbers are allowed in names def test_instance(self): - Point = NamedTuple('Point', 'x y') + Point = named_tuple('Point', 'x y') p = Point(11, 22) self.assertEqual(p, Point(x=11, y=22)) self.assertEqual(p, Point(11, y=22)) @@ -36,17 +38,17 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual(p.__asdict__(), dict(x=11, y=22)) # test __dict__ method # verify that field string can have commas - Point = NamedTuple('Point', 'x, y') + Point = named_tuple('Point', 'x, y') p = Point(x=11, y=22) self.assertEqual(repr(p), 'Point(x=11, y=22)') # verify that fieldspec can be a non-string sequence - Point = NamedTuple('Point', ('x', 'y')) + Point = named_tuple('Point', ('x', 'y')) p = Point(x=11, y=22) self.assertEqual(repr(p), 'Point(x=11, y=22)') def test_tupleness(self): - Point = NamedTuple('Point', 'x y') + Point = named_tuple('Point', 'x y') p = Point(11, 22) self.assert_(isinstance(p, tuple)) @@ -66,9 +68,9 @@ class TestNamedTuple(unittest.TestCase): def test_odd_sizes(self): - Zero = NamedTuple('Zero', '') + Zero = named_tuple('Zero', '') self.assertEqual(Zero(), ()) - Dot = NamedTuple('Dot', 'd') + Dot = named_tuple('Dot', 'd') self.assertEqual(Dot(1), (1,)) def test_main(verbose=None): diff --git a/Misc/NEWS b/Misc/NEWS index 2a1c5119e11..4492590bef6 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -544,7 +544,7 @@ Library - Added heapq.merge() for merging sorted input streams. -- Added collections.NamedTuple() for assigning field names to tuples. +- Added collections.named_tuple() for assigning field names to tuples. - Added itertools.izip_longest().