diff -r bc322469a7a8 Doc/library/collections.rst --- a/Doc/library/collections.rst Mon Dec 10 20:22:55 2012 -0800 +++ b/Doc/library/collections.rst Wed Dec 12 19:11:42 2012 +0200 @@ -731,7 +731,8 @@ self-documenting code. They can be used wherever regular tuples are used, and they add the ability to access fields by name instead of position index. -.. function:: namedtuple(typename, field_names, verbose=False, rename=False) +.. function:: namedtuple(typename, field_names, verbose=False, rename=False, *, + doc=None, field_docs=None) Returns a new tuple subclass named *typename*. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as @@ -758,12 +759,19 @@ built. This option is outdated; instead, it is simpler to print the :attr:`_source` attribute. + *doc* argument specifies a class docstring. + + The *field_docs* are a sequence of strings wich contains fields docstrings. + Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples. .. versionchanged:: 3.1 Added support for *rename*. + .. versionchanged:: 3.4 + Added support for *doc* and *field_docs*. + .. doctest:: :options: +NORMALIZE_WHITESPACE diff -r bc322469a7a8 Lib/collections/__init__.py --- a/Lib/collections/__init__.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/collections/__init__.py Wed Dec 12 19:11:42 2012 +0200 @@ -242,7 +242,7 @@ from collections import OrderedDict class {typename}(tuple): - '{typename}({arg_list})' + {doc!r} __slots__ = () @@ -284,13 +284,18 @@ {field_defs} ''' +_doc_template = '{typename}({arg_list})' + _repr_template = '{name}=%r' _field_template = '''\ - {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') + {name} = _property(_itemgetter({index:d}), doc={doc!r}) ''' -def namedtuple(typename, field_names, verbose=False, rename=False): +_field_doc_template = 'Alias for field number {index:d}' + +def namedtuple(typename, field_names, verbose=False, rename=False, *, + doc=None, field_docs=None): """Returns a new subclass of tuple with named fields. >>> Point = namedtuple('Point', ['x', 'y']) @@ -318,7 +323,12 @@ # message or automatically replace the field name with a valid name. if isinstance(field_names, str): field_names = field_names.replace(',', ' ').split() - field_names = list(map(str, field_names)) + else: + field_names = list(map(str, field_names)) + if field_docs is None: + field_docs = [None] * len(field_names) + if len(field_docs) != len(field_names): + raise ValueError("field_docs and field_names must be same length") if rename: seen = set() for index, name in enumerate(field_names): @@ -350,15 +360,23 @@ seen.add(name) # Fill-in the class template + field_names = tuple(field_names) + arg_list = repr(field_names).replace("'", "")[1:-1] + if doc is None: + doc = _doc_template.format(typename=typename, arg_list=arg_list) class_definition = _class_template.format( typename = typename, - field_names = tuple(field_names), + doc = doc, + field_names = field_names, num_fields = len(field_names), - arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], + arg_list = arg_list, repr_fmt = ', '.join(_repr_template.format(name=name) for name in field_names), - field_defs = '\n'.join(_field_template.format(index=index, name=name) - for index, name in enumerate(field_names)) + field_defs = '\n'.join( + _field_template.format(index=index, name=name, + doc=_field_doc_template.format(index=index, name=name) + if fdoc is None else fdoc) + for index, (name, fdoc) in enumerate(zip(field_names, field_docs))) ) # Execute the template string in a temporary namespace and support diff -r bc322469a7a8 Lib/test/test_collections.py --- a/Lib/test/test_collections.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/test/test_collections.py Wed Dec 12 19:11:42 2012 +0200 @@ -140,6 +140,13 @@ self.assertRaises(ValueError, namedtuple, 'abc', '_efg ghi') # field with leading underscore self.assertRaises(ValueError, namedtuple, 'abc', 'efg efg ghi') # duplicate field + self.assertRaises(TypeError, namedtuple, + 'abc', 'efg ghi', field_docs=42) # non-sequence + self.assertRaises(ValueError, namedtuple, + 'abc', 'efg ghi', field_docs=['x']) # too few field docstrings + self.assertRaises(ValueError, namedtuple, + 'abc', 'efg ghi', field_docs=['x', 'y', 'z']) # too many field docstrings + namedtuple('Point0', 'x1 y2') # Verify that numbers are allowed in names namedtuple('_', 'a b c') # Test leading underscores in a typename @@ -156,6 +163,15 @@ def test_factory_doc_attr(self): Point = namedtuple('Point', 'x y') self.assertEqual(Point.__doc__, 'Point(x, y)') + self.assertEqual(Point.x.__doc__, 'Alias for field number 0') + self.assertEqual(Point.y.__doc__, 'Alias for field number 1') + + Point = namedtuple('Point', 'x y', + doc='Point: 2-dimensional coordinate', + field_docs=['abscissa', 'ordinate']) + self.assertEqual(Point.__doc__, 'Point: 2-dimensional coordinate') + self.assertEqual(Point.x.__doc__, 'abscissa') + self.assertEqual(Point.y.__doc__, 'ordinate') def test_name_fixer(self): for spec, renamed in [