Kaydet (Commit) 322daea7 authored tarafından Raymond Hettinger's avatar Raymond Hettinger

Issue 1818: collections.namedtuple() to support automatic renaming of invalid fieldnames.

üst c5eba1e4
...@@ -617,7 +617,7 @@ Named tuples assign meaning to each position in a tuple and allow for more reada ...@@ -617,7 +617,7 @@ Named tuples assign meaning to each position in a tuple and allow for more reada
self-documenting code. They can be used wherever regular tuples are used, and 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. they add the ability to access fields by name instead of position index.
.. function:: namedtuple(typename, field_names, [verbose]) .. function:: namedtuple(typename, field_names, [verbose], [rename])
Returns a new tuple subclass named *typename*. The new subclass is used to 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 create tuple-like objects that have fields accessible by attribute lookup as
...@@ -635,6 +635,11 @@ they add the ability to access fields by name instead of position index. ...@@ -635,6 +635,11 @@ they add the ability to access fields by name instead of position index.
a :mod:`keyword` such as *class*, *for*, *return*, *global*, *pass*, *print*, a :mod:`keyword` such as *class*, *for*, *return*, *global*, *pass*, *print*,
or *raise*. or *raise*.
If *rename* is true, invalid fieldnames are automatically replaced
with positional names. For example, ``['abc', 'def', 'ghi', 'abc']`` is
converted to ``['abc', '_2', 'ghi', '_4']``, 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 just before being built.
Named tuple instances do not have per-instance dictionaries, so they are Named tuple instances do not have per-instance dictionaries, so they are
...@@ -642,6 +647,9 @@ they add the ability to access fields by name instead of position index. ...@@ -642,6 +647,9 @@ they add the ability to access fields by name instead of position index.
.. versionadded:: 2.6 .. versionadded:: 2.6
.. versionchanged:: 2.7
added support for *rename*.
Example: Example:
.. doctest:: .. doctest::
......
...@@ -16,7 +16,7 @@ from itertools import repeat as _repeat, chain as _chain, starmap as _starmap, i ...@@ -16,7 +16,7 @@ from itertools import repeat as _repeat, chain as _chain, starmap as _starmap, i
### namedtuple ### namedtuple
################################################################################ ################################################################################
def namedtuple(typename, field_names, verbose=False): def namedtuple(typename, field_names, verbose=False, rename=False):
"""Returns a new subclass of tuple with named fields. """Returns a new subclass of tuple with named fields.
>>> Point = namedtuple('Point', 'x y') >>> Point = namedtuple('Point', 'x y')
...@@ -45,6 +45,16 @@ def namedtuple(typename, field_names, verbose=False): ...@@ -45,6 +45,16 @@ def namedtuple(typename, field_names, verbose=False):
if isinstance(field_names, basestring): if isinstance(field_names, basestring):
field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
field_names = tuple(map(str, field_names)) field_names = tuple(map(str, field_names))
if rename:
names = list(field_names)
seen = set()
for i, name in enumerate(names):
if (not all(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
or not name or name[0].isdigit() or name.startswith('_')
or name in seen):
names[i] = '_%d' % (i+1)
seen.add(name)
field_names = tuple(names)
for name in (typename,) + field_names: for name in (typename,) + field_names:
if not all(c.isalnum() or c=='_' for c in name): if not all(c.isalnum() or c=='_' for c in name):
raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
...@@ -54,7 +64,7 @@ def namedtuple(typename, field_names, verbose=False): ...@@ -54,7 +64,7 @@ def namedtuple(typename, field_names, verbose=False):
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_names = set()
for name in field_names: for name in field_names:
if name.startswith('_'): 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_names:
raise ValueError('Encountered duplicate field name: %r' % name) raise ValueError('Encountered duplicate field name: %r' % name)
......
...@@ -44,6 +44,17 @@ class TestNamedTuple(unittest.TestCase): ...@@ -44,6 +44,17 @@ class TestNamedTuple(unittest.TestCase):
self.assertRaises(TypeError, Point._make, [11]) # catch too few args self.assertRaises(TypeError, Point._make, [11]) # catch too few args
self.assertRaises(TypeError, Point._make, [11, 22, 33]) # catch too many args self.assertRaises(TypeError, Point._make, [11, 22, 33]) # catch too many args
def test_name_fixer(self):
for spec, renamed in [
[('efg', 'g%hi'), ('efg', '_2')], # field with non-alpha char
[('abc', 'class'), ('abc', '_2')], # field has keyword
[('8efg', '9ghi'), ('_1', '_2')], # field starts with digit
[('abc', '_efg'), ('abc', '_2')], # field with leading underscore
[('abc', 'efg', 'efg', 'ghi'), ('abc', 'efg', '_3', 'ghi')], # duplicate field
[('abc', '', 'x'), ('abc', '_2', 'x')], # fieldname is a space
]:
self.assertEqual(namedtuple('NT', spec, rename=True)._fields, renamed)
def test_instance(self): def test_instance(self):
Point = namedtuple('Point', 'x y') Point = namedtuple('Point', 'x y')
p = Point(11, 22) p = Point(11, 22)
......
...@@ -155,6 +155,10 @@ Library ...@@ -155,6 +155,10 @@ Library
- Issue #5122: Synchronize tk load failure check to prevent a potential - Issue #5122: Synchronize tk load failure check to prevent a potential
deadlock. deadlock.
- Issue #1818: collections.namedtuple() now supports a keyword argument
'rename' which lets invalid fieldnames be automatically converted to
positional names in the form, _1, _2, ...
- Issue #4890: Handle empty text search pattern in Tkinter.Text.search. - Issue #4890: Handle empty text search pattern in Tkinter.Text.search.
- Issue #5170: Fixed Unicode output bug in logging and added test case. - Issue #5170: Fixed Unicode output bug in logging and added test case.
......
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