Commit d36a60e1 authored by Raymond Hettinger's avatar Raymond Hettinger

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.
parent bf10c473
......@@ -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
......
......@@ -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))
......
......@@ -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')
......
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