diff options
-rw-r--r-- | Doc/library/collections.rst | 37 | ||||
-rw-r--r-- | Lib/collections.py | 3 | ||||
-rw-r--r-- | Lib/test/test_collections.py | 4 |
3 files changed, 27 insertions, 17 deletions
diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 5fcb293..9e8f3e3 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -391,6 +391,8 @@ Example:: def __new__(cls, x, y): return tuple.__new__(cls, (x, y)) + _cast = classmethod(tuple.__new__) + def __repr__(self): return 'Point(x=%r, y=%r)' % self @@ -400,7 +402,7 @@ Example:: def _replace(self, **kwds): 'Return a new Point object replacing specified fields with new values' - return Point(*map(kwds.get, ('x', 'y'), self)) + return Point._cast(map(kwds.get, ('x', 'y'), self)) @property def _fields(self): @@ -425,33 +427,30 @@ by the :mod:`csv` or :mod:`sqlite3` modules:: EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade') - from itertools import starmap import csv - for emp in starmap(EmployeeRecord, csv.reader(open("employees.csv", "rb"))): + for emp in map(EmployeeRecord._cast, csv.reader(open("employees.csv", "rb"))): print emp.name, emp.title 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()): + for emp in map(EmployeeRecord._cast, cursor.fetchall()): print emp.name, emp.title -When casting a single record to a named tuple, use the star-operator [#]_ to unpack -the values:: +In addition to the methods inherited from tuples, named tuples support +three additonal methods and a read-only attribute. - >>> t = [11, 22] - >>> Point(*t) # the star-operator unpacks any iterable object - Point(x=11, y=22) +.. method:: namedtuple._cast(iterable) -When casting a dictionary to a named tuple, use the double-star-operator:: + Class method returning a new instance taking the positional arguments from the *iterable*. + Useful for casting existing sequences and iterables to named tuples: - >>> d = {'x': 11, 'y': 22} - >>> Point(**d) - Point(x=11, y=22) +:: -In addition to the methods inherited from tuples, named tuples support -two additonal methods and a read-only attribute. + >>> t = [11, 22] + >>> Point._cast(t) + Point(x=11, y=22) .. method:: somenamedtuple._asdict() @@ -498,6 +497,12 @@ function: >>> getattr(p, 'x') 11 +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) + Since a named tuple is a regular Python class, it is easy to add or change functionality. For example, the display format can be changed by overriding the :meth:`__repr__` method: @@ -520,5 +525,5 @@ and customizing it with :meth:`_replace`: .. rubric:: Footnotes -.. [#] For information on the star-operator see +.. [#] For information on the double-star-operator see :ref:`tut-unpacking-arguments` and :ref:`calls`. diff --git a/Lib/collections.py b/Lib/collections.py index 3d98f7c..c6d0d0f 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -62,6 +62,7 @@ def namedtuple(typename, field_names, verbose=False): __slots__ = () \n def __new__(cls, %(argtxt)s): return tuple.__new__(cls, (%(argtxt)s)) \n + _cast = classmethod(tuple.__new__) \n def __repr__(self): return '%(typename)s(%(reprtxt)s)' %% self \n def _asdict(t): @@ -69,7 +70,7 @@ def namedtuple(typename, field_names, verbose=False): return {%(dicttxt)s} \n def _replace(self, **kwds): 'Return a new %(typename)s object replacing specified fields with new values' - return %(typename)s(*map(kwds.get, %(field_names)r, self)) \n + return %(typename)s._cast(map(kwds.get, %(field_names)r, self)) \n @property def _fields(self): return %(field_names)r \n\n''' % locals() diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 73ae62a..edffbbe 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -46,6 +46,7 @@ class TestNamedTuple(unittest.TestCase): 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)) + self.assertEqual(p, Point._cast([11, 22])) # test _cast classmethod self.assertEqual(p._fields, ('x', 'y')) # test _fields attribute self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method @@ -90,12 +91,14 @@ class TestNamedTuple(unittest.TestCase): def test_odd_sizes(self): Zero = namedtuple('Zero', '') self.assertEqual(Zero(), ()) + self.assertEqual(Zero._cast([]), ()) self.assertEqual(repr(Zero()), 'Zero()') self.assertEqual(Zero()._asdict(), {}) self.assertEqual(Zero()._fields, ()) Dot = namedtuple('Dot', 'd') self.assertEqual(Dot(1), (1,)) + self.assertEqual(Dot._cast([1]), (1,)) self.assertEqual(Dot(1).d, 1) self.assertEqual(repr(Dot(1)), 'Dot(d=1)') self.assertEqual(Dot(1)._asdict(), {'d':1}) @@ -108,6 +111,7 @@ class TestNamedTuple(unittest.TestCase): Big = namedtuple('Big', names) b = Big(*range(n)) self.assertEqual(b, tuple(range(n))) + self.assertEqual(Big._cast(range(n)), tuple(range(n))) for pos, name in enumerate(names): self.assertEqual(getattr(b, name), pos) repr(b) # make sure repr() doesn't blow-up |