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.
diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst
index 28d48bf..7b639b3 100644
--- a/Doc/library/collections.rst
+++ b/Doc/library/collections.rst
@@ -374,8 +374,8 @@
 
    .. 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 @@
 
    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 @@
       >>> 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
diff --git a/Lib/collections.py b/Lib/collections.py
index 4a860dd..c2b1176 100644
--- a/Lib/collections.py
+++ b/Lib/collections.py
@@ -8,33 +8,42 @@
     """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 @@
 
 
 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))
 
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index f5dad7d..94015b4 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -30,6 +30,13 @@
         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')