Commit d9a567bf authored by Raymond Hettinger's avatar Raymond Hettinger

Users demand iterable input for named tuples. The author capitulates.

parent 87ce5ae3
...@@ -391,6 +391,8 @@ Example:: ...@@ -391,6 +391,8 @@ Example::
def __new__(cls, x, y): def __new__(cls, x, y):
return tuple.__new__(cls, (x, y)) return tuple.__new__(cls, (x, y))
_cast = classmethod(tuple.__new__)
def __repr__(self): def __repr__(self):
return 'Point(x=%r, y=%r)' % self return 'Point(x=%r, y=%r)' % self
...@@ -400,7 +402,7 @@ Example:: ...@@ -400,7 +402,7 @@ Example::
def _replace(self, **kwds): def _replace(self, **kwds):
'Return a new Point object replacing specified fields with new values' '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 @property
def _fields(self): def _fields(self):
...@@ -425,33 +427,30 @@ by the :mod:`csv` or :mod:`sqlite3` modules:: ...@@ -425,33 +427,30 @@ by the :mod:`csv` or :mod:`sqlite3` modules::
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade') EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
from itertools import starmap
import csv 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 print emp.name, emp.title
import sqlite3 import sqlite3
conn = sqlite3.connect('/companydata') conn = sqlite3.connect('/companydata')
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees') 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 print emp.name, emp.title
When casting a single record to a named tuple, use the star-operator [#]_ to unpack In addition to the methods inherited from tuples, named tuples support
the values:: three additonal methods and a read-only attribute.
>>> t = [11, 22] .. method:: namedtuple._cast(iterable)
>>> Point(*t) # the star-operator unpacks any iterable object
Point(x=11, y=22)
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 >>> t = [11, 22]
two additonal methods and a read-only attribute. >>> Point._cast(t)
Point(x=11, y=22)
.. method:: somenamedtuple._asdict() .. method:: somenamedtuple._asdict()
...@@ -498,6 +497,12 @@ function: ...@@ -498,6 +497,12 @@ function:
>>> getattr(p, 'x') >>> getattr(p, 'x')
11 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 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 functionality. For example, the display format can be changed by overriding
the :meth:`__repr__` method: the :meth:`__repr__` method:
...@@ -520,5 +525,5 @@ and customizing it with :meth:`_replace`: ...@@ -520,5 +525,5 @@ and customizing it with :meth:`_replace`:
.. rubric:: Footnotes .. rubric:: Footnotes
.. [#] For information on the star-operator see .. [#] For information on the double-star-operator see
:ref:`tut-unpacking-arguments` and :ref:`calls`. :ref:`tut-unpacking-arguments` and :ref:`calls`.
...@@ -62,6 +62,7 @@ def namedtuple(typename, field_names, verbose=False): ...@@ -62,6 +62,7 @@ def namedtuple(typename, field_names, verbose=False):
__slots__ = () \n __slots__ = () \n
def __new__(cls, %(argtxt)s): def __new__(cls, %(argtxt)s):
return tuple.__new__(cls, (%(argtxt)s)) \n return tuple.__new__(cls, (%(argtxt)s)) \n
_cast = classmethod(tuple.__new__) \n
def __repr__(self): def __repr__(self):
return '%(typename)s(%(reprtxt)s)' %% self \n return '%(typename)s(%(reprtxt)s)' %% self \n
def _asdict(t): def _asdict(t):
...@@ -69,7 +70,7 @@ def namedtuple(typename, field_names, verbose=False): ...@@ -69,7 +70,7 @@ def namedtuple(typename, field_names, verbose=False):
return {%(dicttxt)s} \n return {%(dicttxt)s} \n
def _replace(self, **kwds): def _replace(self, **kwds):
'Return a new %(typename)s object replacing specified fields with new values' '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 @property
def _fields(self): def _fields(self):
return %(field_names)r \n\n''' % locals() return %(field_names)r \n\n''' % locals()
......
...@@ -46,6 +46,7 @@ class TestNamedTuple(unittest.TestCase): ...@@ -46,6 +46,7 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(repr(p), 'Point(x=11, y=22)') self.assertEqual(repr(p), 'Point(x=11, y=22)')
self.assert_('__dict__' not in dir(p)) # verify instance has no dict self.assert_('__dict__' not in dir(p)) # verify instance has no dict
self.assert_('__weakref__' not in dir(p)) 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._fields, ('x', 'y')) # test _fields attribute
self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method
self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method
...@@ -90,12 +91,14 @@ class TestNamedTuple(unittest.TestCase): ...@@ -90,12 +91,14 @@ class TestNamedTuple(unittest.TestCase):
def test_odd_sizes(self): def test_odd_sizes(self):
Zero = namedtuple('Zero', '') Zero = namedtuple('Zero', '')
self.assertEqual(Zero(), ()) self.assertEqual(Zero(), ())
self.assertEqual(Zero._cast([]), ())
self.assertEqual(repr(Zero()), 'Zero()') self.assertEqual(repr(Zero()), 'Zero()')
self.assertEqual(Zero()._asdict(), {}) self.assertEqual(Zero()._asdict(), {})
self.assertEqual(Zero()._fields, ()) self.assertEqual(Zero()._fields, ())
Dot = namedtuple('Dot', 'd') Dot = namedtuple('Dot', 'd')
self.assertEqual(Dot(1), (1,)) self.assertEqual(Dot(1), (1,))
self.assertEqual(Dot._cast([1]), (1,))
self.assertEqual(Dot(1).d, 1) self.assertEqual(Dot(1).d, 1)
self.assertEqual(repr(Dot(1)), 'Dot(d=1)') self.assertEqual(repr(Dot(1)), 'Dot(d=1)')
self.assertEqual(Dot(1)._asdict(), {'d':1}) self.assertEqual(Dot(1)._asdict(), {'d':1})
...@@ -108,6 +111,7 @@ class TestNamedTuple(unittest.TestCase): ...@@ -108,6 +111,7 @@ class TestNamedTuple(unittest.TestCase):
Big = namedtuple('Big', names) Big = namedtuple('Big', names)
b = Big(*range(n)) b = Big(*range(n))
self.assertEqual(b, tuple(range(n))) self.assertEqual(b, tuple(range(n)))
self.assertEqual(Big._cast(range(n)), tuple(range(n)))
for pos, name in enumerate(names): for pos, name in enumerate(names):
self.assertEqual(getattr(b, name), pos) self.assertEqual(getattr(b, name), pos)
repr(b) # make sure repr() doesn't blow-up repr(b) # make sure repr() doesn't blow-up
......
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