Expose the namedtuple source with a _source attribute.

This commit is contained in:
Raymond Hettinger 2011-03-23 12:52:23 -07:00
parent 843a751369
commit 2ebea41d31
3 changed files with 20 additions and 53 deletions

View File

@ -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 converted to ``['abc', '_1', 'ghi', '_3']``, eliminating the keyword
``def`` and the duplicate fieldname ``abc``. ``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 Named tuple instances do not have per-instance dictionaries, so they are
lightweight and require no more memory than regular tuples. 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 >>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y']) >>> 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)'
<BLANKLINE>
__slots__ = ()
<BLANKLINE>
_fields = ('x', 'y')
<BLANKLINE>
def __new__(_cls, x, y):
'Create a new instance of Point(x, y)'
return _tuple.__new__(_cls, (x, y))
<BLANKLINE>
@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
<BLANKLINE>
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + '(x=%r, y=%r)' % self
<BLANKLINE>
def _asdict(self):
'Return a new OrderedDict which maps field names to their values'
return OrderedDict(zip(self._fields, self))
<BLANKLINE>
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
<BLANKLINE>
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self)
<BLANKLINE>
x = _property(_itemgetter(0), doc='Alias for field number 0')
<BLANKLINE>
y = _property(_itemgetter(1), doc='Alias for field number 1')
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments >>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22) >>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33 33
@ -782,7 +738,7 @@ by the :mod:`csv` or :mod:`sqlite3` modules::
print(emp.name, emp.title) print(emp.name, emp.title)
In addition to the methods inherited from tuples, named tuples support 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. field names, the method and attribute names start with an underscore.
.. classmethod:: somenamedtuple._make(iterable) .. 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(): >>> for partnum, record in inventory.items():
... inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now()) ... 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 .. attribute:: somenamedtuple._fields
Tuple of strings listing the field names. Useful for introspection Tuple of strings listing the field names. Useful for introspection

View File

@ -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) raise ValueError('Type names and field names cannot be a keyword: %r' % name)
if name[0].isdigit(): if name[0].isdigit():
raise ValueError('Type names and field names cannot start with a number: %r' % name) raise ValueError('Type names and field names cannot start with a number: %r' % name)
seen_names = set() seen = set()
for name in field_names: for name in field_names:
if name.startswith('_') and not rename: if name.startswith('_') and not rename:
raise ValueError('Field names cannot start with an underscore: %r' % name) 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) raise ValueError('Encountered duplicate field name: %r' % name)
seen_names.add(name) seen.add(name)
# Fill-in the class template # Fill-in the class template
class_definition = _class_template.format( 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) field_defs = '\n'.join(_field_template.format(index=index, name=name)
for index, name in enumerate(field_names)) for index, name in enumerate(field_names))
) )
if verbose:
print(class_definition)
# Execute the class definition string in a temporary namespace and # Execute the class definition string in a temporary namespace and
# support tracing utilities by setting a value for frame.f_globals['__name__'] # 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: except SyntaxError as e:
raise SyntaxError(e.msg + ':\n\n' + class_definition) raise SyntaxError(e.msg + ':\n\n' + class_definition)
result = namespace[typename] 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 # 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 # where the named tuple is created. Bypass this step in enviroments where

View File

@ -127,6 +127,7 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Point.__module__, __name__) self.assertEqual(Point.__module__, __name__)
self.assertEqual(Point.__getitem__, tuple.__getitem__) self.assertEqual(Point.__getitem__, tuple.__getitem__)
self.assertEqual(Point._fields, ('x', 'y')) 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, 'abc%', 'efg ghi') # type has non-alpha char
self.assertRaises(ValueError, namedtuple, 'class', 'efg ghi') # type has keyword self.assertRaises(ValueError, namedtuple, 'class', 'efg ghi') # type has keyword