Sync-up named tuples with the latest version of the ASPN recipe.

Allows optional commas in the field-name spec (help when named tuples are used in conjuction with sql queries).
Adds the __fields__ attribute for introspection and to support conversion to dictionary form.
Adds a  __replace__() method similar to str.replace() but using a named field as a target.
Clean-up spelling and presentation in doc-strings.
This commit is contained in:
Raymond Hettinger 2007-09-17 00:55:00 +00:00
parent bf10c47389
commit d36a60e1e3
3 changed files with 61 additions and 13 deletions

View File

@ -374,8 +374,8 @@ Setting the :attr:`default_factory` to :class:`set` makes the
.. versionadded:: 2.6
The *fieldnames* are specified in a single string and are separated by spaces.
Any valid Python identifier may be used for a field name.
The *fieldnames* are specified in a single string and are separated by spaces
and/or commas. Any valid Python identifier may be used for a field name.
Example::
@ -395,7 +395,7 @@ Setting the :attr:`default_factory` to :class:`set` makes the
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. Named tuples can also be used to assign field names to tuples returned
code. Named tuples can also be used to assign field names to tuples returned
by the :mod:`csv` or :mod:`sqlite3` modules. For example::
from itertools import starmap
@ -412,6 +412,38 @@ Setting the :attr:`default_factory` to :class:`set` makes the
>>> print Color(*m.popitem())
Color(name='blue', code=3)
In addition to the methods inherited from tuples, named tuples support
an additonal method and an informational read-only attribute.
.. method:: somenamedtuple.replace(field, value)
Return a new instance of the named tuple with *field* replaced with *value*.
Examples::
>>> p = Point(x=11, y=22)
>>> p.__replace__('x', 33)
Point(x=33, y=22)
>>> for recordnum, record in inventory:
... inventory[recordnum] = record.replace('total', record.price * record.quantity)
.. attribute:: somenamedtuple.__fields__
Return a tuple of strings listing the field names. This is useful for introspection,
for converting a named tuple instance to a dictionary, and for creating new named tuple
types from existing types.
Examples::
>>> dict(zip(p.__fields__, p)) # make a dictionary from a named tuple instance
{'y': 20, 'x': 10}
>>> ColorPoint = NamedTuple('ColorPoint', ' '.join(Point.__fields__) + ' color')
>>> ColorPoint(10, 20, 'red')
ColorPoint(x=10, y=20, color='red')
.. rubric:: Footnotes
.. [#] For information on the star-operator see

View File

@ -8,33 +8,42 @@ 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.__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)
>>> 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 = p # unpacks just like a tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessable by name
>>> p.x + p.y # fields also accessable by name
33
>>> p # readable __repr__ with name=value style
>>> p # readable __repr__ with name=value style
Point(x=11, y=22)
>>> p.__replace__('x', 100) # __replace__() is like str.replace() but targets a named field
Point(x=100, y=22)
>>> d = dict(zip(p.__fields__, p)) # use __fields__ to make a dictionary
>>> d['x']
11
"""
field_names = s.split()
if not ''.join([typename] + field_names).replace('_', '').isalnum():
field_names = tuple(s.replace(',', ' ').split()) # names separated by spaces and/or commas
if not ''.join((typename,) + field_names).replace('_', '').isalnum():
raise ValueError('Type names and field names can only contain alphanumeric characters and underscores')
argtxt = ', '.join(field_names)
reprtxt = ', '.join('%s=%%r' % name for name in field_names)
template = '''class %(typename)s(tuple):
'%(typename)s(%(argtxt)s)'
__slots__ = ()
__fields__ = %(field_names)r
def __new__(cls, %(argtxt)s):
return tuple.__new__(cls, (%(argtxt)s,))
def __repr__(self):
return '%(typename)s(%(reprtxt)s)' %% self
def __replace__(self, field, value):
'Return a new %(typename)s object replacing one field with a new value'
return %(typename)s(**dict(zip(%(field_names)r, self) + [(field, value)]))
''' % locals()
for i, name in enumerate(field_names):
template += '\n %s = property(itemgetter(%d))\n' % (name, i)
@ -51,9 +60,9 @@ def NamedTuple(typename, s):
if __name__ == '__main__':
# verify that instances are pickable
# verify that instances can be pickled
from cPickle import loads, dumps
Point = NamedTuple('Point', 'x y')
Point = NamedTuple('Point', 'x, y')
p = Point(x=10, y=20)
assert p == loads(dumps(p))

View File

@ -30,6 +30,13 @@ 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.__fields__, ('x', 'y')) # test __fields__ attribute
self.assertEqual(p.__replace__('x', 1), (1, 22)) # test __replace__ method
# verify that field string can have commas
Point = NamedTuple('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')