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

This commit is contained in:
Raymond Hettinger 2007-12-18 23:51:15 +00:00
parent 0a8143f646
commit 85dfcf3530
3 changed files with 28 additions and 18 deletions

View File

@ -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::
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,34 +427,31 @@ 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.
.. method:: namedtuple._cast(iterable)
Class method returning a new instance taking the positional arguments from the *iterable*.
Useful for casting existing sequences and iterables to named tuples:
::
>>> t = [11, 22] >>> t = [11, 22]
>>> Point(*t) # the star-operator unpacks any iterable object >>> Point._cast(t)
Point(x=11, y=22) Point(x=11, y=22)
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
two additonal methods and a read-only attribute.
.. method:: somenamedtuple._asdict() .. method:: somenamedtuple._asdict()
Return a new dict which maps field names to their corresponding values: Return a new dict which maps field names to their corresponding values:
@ -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`:
.. 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`.

View File

@ -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):
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()

View File

@ -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):
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):
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