From 2ebea41d315bb42a6d6983137bf5fdb01d3f1a95 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 23 Mar 2011 12:52:23 -0700 Subject: [PATCH] Expose the namedtuple source with a _source attribute. --- Doc/library/collections.rst | 61 ++++++++---------------------------- Lib/collections/__init__.py | 11 ++++--- Lib/test/test_collections.py | 1 + 3 files changed, 20 insertions(+), 53 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 411d5f6832f..e531d40c967 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -694,7 +694,9 @@ they add the ability to access fields by name instead of position index. converted to ``['abc', '_1', 'ghi', '_3']``, eliminating the keyword ``def`` and the duplicate fieldname ``abc``. - If *verbose* is true, the class definition is printed just before being built. + If *verbose* is true, the class definition is printed after it is + built. This option is outdated; instead, it is simpler to print the + :attr:`_source` attribute. Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples. @@ -708,52 +710,6 @@ they add the ability to access fields by name instead of position index. >>> # Basic example >>> Point = namedtuple('Point', ['x', 'y']) - >>> p = Point(x=10, y=11) - - >>> # Example using the verbose option to print the class definition - >>> Point = namedtuple('Point', ['x', 'y'], verbose=True) - class Point(tuple): - 'Point(x, y)' - - __slots__ = () - - _fields = ('x', 'y') - - def __new__(_cls, x, y): - 'Create a new instance of Point(x, y)' - return _tuple.__new__(_cls, (x, y)) - - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - 'Make a new Point object from a sequence or iterable' - result = new(cls, iterable) - if len(result) != 2: - raise TypeError('Expected 2 arguments, got %d' % len(result)) - return result - - def __repr__(self): - 'Return a nicely formatted representation string' - return self.__class__.__name__ + '(x=%r, y=%r)' % self - - def _asdict(self): - 'Return a new OrderedDict which maps field names to their values' - return OrderedDict(zip(self._fields, self)) - - def _replace(_self, **kwds): - 'Return a new Point object replacing specified fields with new values' - result = _self._make(map(kwds.pop, ('x', 'y'), _self)) - if kwds: - raise ValueError('Got unexpected field names: %r' % list(kwds)) - return result - - def __getnewargs__(self): - 'Return self as a plain tuple. Used by copy and pickle.' - return tuple(self) - - x = _property(_itemgetter(0), doc='Alias for field number 0') - - y = _property(_itemgetter(1), doc='Alias for field number 1') - >>> p = Point(11, y=22) # instantiate with positional or keyword arguments >>> p[0] + p[1] # indexable like the plain tuple (11, 22) 33 @@ -782,7 +738,7 @@ by the :mod:`csv` or :mod:`sqlite3` modules:: print(emp.name, emp.title) In addition to the methods inherited from tuples, named tuples support -three additional methods and one attribute. To prevent conflicts with +three additional methods and two attributes. To prevent conflicts with field names, the method and attribute names start with an underscore. .. classmethod:: somenamedtuple._make(iterable) @@ -820,6 +776,15 @@ field names, the method and attribute names start with an underscore. >>> for partnum, record in inventory.items(): ... inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now()) +.. attribute:: somenamedtuple._source + + A string with the pure Python source code used to create the named + tuple class. The source makes the named tuple self-documenting. + It can be printed, executed using :func:`exec`, or saved to a file + and imported. + + .. versionadded:: 3.3 + .. attribute:: somenamedtuple._fields Tuple of strings listing the field names. Useful for introspection diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index b75b4d73083..652e4f1e38e 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -332,13 +332,13 @@ def namedtuple(typename, field_names, verbose=False, rename=False): raise ValueError('Type names and field names cannot be a keyword: %r' % name) if name[0].isdigit(): raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() + seen = set() for name in field_names: if name.startswith('_') and not rename: raise ValueError('Field names cannot start with an underscore: %r' % name) - if name in seen_names: + if name in seen: raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) + seen.add(name) # Fill-in the class template class_definition = _class_template.format( @@ -350,8 +350,6 @@ def namedtuple(typename, field_names, verbose=False, rename=False): field_defs = '\n'.join(_field_template.format(index=index, name=name) for index, name in enumerate(field_names)) ) - if verbose: - print(class_definition) # Execute the class definition string in a temporary namespace and # support tracing utilities by setting a value for frame.f_globals['__name__'] @@ -361,6 +359,9 @@ def namedtuple(typename, field_names, verbose=False, rename=False): except SyntaxError as e: raise SyntaxError(e.msg + ':\n\n' + class_definition) result = namespace[typename] + result._source = class_definition + if verbose: + print(result._source) # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in enviroments where diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index d71fb01dec8..cdc7db9a7f8 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -127,6 +127,7 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual(Point.__module__, __name__) self.assertEqual(Point.__getitem__, tuple.__getitem__) self.assertEqual(Point._fields, ('x', 'y')) + self.assertIn('class Point(tuple)', Point._source) self.assertRaises(ValueError, namedtuple, 'abc%', 'efg ghi') # type has non-alpha char self.assertRaises(ValueError, namedtuple, 'class', 'efg ghi') # type has keyword