]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Fix field name conflicts for named tuples.
authorRaymond Hettinger <python@rcn.com>
Wed, 27 May 2009 02:09:58 +0000 (02:09 +0000)
committerRaymond Hettinger <python@rcn.com>
Wed, 27 May 2009 02:09:58 +0000 (02:09 +0000)
Doc/library/collections.rst
Lib/collections.py
Lib/test/test_collections.py
Misc/NEWS

index 6b6ac11606f2a75f7f0c504bab833692b9a2345e..c7660b8d436e1ce3011c43965f4eff1055591301 100644 (file)
@@ -502,8 +502,8 @@ Example:
    <BLANKLINE>
            _fields = ('x', 'y')
    <BLANKLINE>
-           def __new__(cls, x, y):
-               return tuple.__new__(cls, (x, y))
+           def __new__(_cls, x, y):
+               return _tuple.__new__(_cls, (x, y))
    <BLANKLINE>
            @classmethod
            def _make(cls, iterable, new=tuple.__new__, len=len):
@@ -520,9 +520,9 @@ Example:
                'Return a new dict which maps field names to their values'
                return {'x': t[0], 'y': t[1]}
    <BLANKLINE>
-           def _replace(self, **kwds):
+           def _replace(_self, **kwds):
                'Return a new Point object replacing specified fields with new values'
-               result = self._make(map(kwds.pop, ('x', 'y'), self))
+               result = _self._make(map(kwds.pop, ('x', 'y'), _self))
                if kwds:
                    raise ValueError('Got unexpected field names: %r' % kwds.keys())
                return result
@@ -530,8 +530,8 @@ Example:
            def __getnewargs__(self):
                return tuple(self)
    <BLANKLINE>
-           x = property(itemgetter(0))
-           y = property(itemgetter(1))
+           x = _property(_itemgetter(0))
+           y = _property(_itemgetter(1))
 
    >>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
    >>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
index 1d950b6d43d651dd84efad0eec82bf8920a1acc0..5a7d76304b355f13816f00e3fa18e581838683b6 100644 (file)
@@ -68,8 +68,8 @@ def namedtuple(typename, field_names, verbose=False):
         '%(typename)s(%(argtxt)s)' \n
         __slots__ = () \n
         _fields = %(field_names)r \n
-        def __new__(cls, %(argtxt)s):
-            return tuple.__new__(cls, (%(argtxt)s)) \n
+        def __new__(_cls, %(argtxt)s):
+            return _tuple.__new__(_cls, (%(argtxt)s)) \n
         @classmethod
         def _make(cls, iterable, new=tuple.__new__, len=len):
             'Make a new %(typename)s object from a sequence or iterable'
@@ -82,22 +82,23 @@ def namedtuple(typename, field_names, verbose=False):
         def _asdict(t):
             'Return a new dict which maps field names to their values'
             return {%(dicttxt)s} \n
-        def _replace(self, **kwds):
+        def _replace(_self, **kwds):
             'Return a new %(typename)s object replacing specified fields with new values'
-            result = self._make(map(kwds.pop, %(field_names)r, self))
+            result = _self._make(map(kwds.pop, %(field_names)r, _self))
             if kwds:
                 raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
             return result \n
         def __getnewargs__(self):
             return tuple(self) \n\n''' % locals()
     for i, name in enumerate(field_names):
-        template += '        %s = property(itemgetter(%d))\n' % (name, i)
+        template += '        %s = _property(_itemgetter(%d))\n' % (name, i)
     if verbose:
         print(template)
 
     # Execute the template string in a temporary namespace and
     # support tracing utilities by setting a value for frame.f_globals['__name__']
-    namespace = dict(itemgetter=_itemgetter, __name__='namedtuple_%s' % typename)
+    namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
+                     _property=property, _tuple=tuple)
     try:
         exec(template, namespace)
     except SyntaxError as e:
index 0c8f7ff246c09ea528f3fb230b80b1aecbb823ce..19b02bb058b86ea84e142ec1e13f1bb6ef06b11a 100644 (file)
@@ -4,6 +4,8 @@ import unittest, doctest
 from test import support
 from collections import namedtuple
 import pickle, copy
+import keyword
+import re
 from collections import Hashable, Iterable, Iterator
 from collections import Sized, Container, Callable
 from collections import Set, MutableSet
@@ -158,6 +160,44 @@ class TestNamedTuple(unittest.TestCase):
             self.assertEqual(p, q)
             self.assertEqual(p._fields, q._fields)
 
+    def test_name_conflicts(self):
+        # Some names like "self", "cls", "tuple", "itemgetter", and "property"
+        # failed when used as field names.  Test to make sure these now work.
+        T = namedtuple('T', 'itemgetter property self cls tuple')
+        t = T(1, 2, 3, 4, 5)
+        self.assertEqual(t, (1,2,3,4,5))
+        newt = t._replace(itemgetter=10, property=20, self=30, cls=40, tuple=50)
+        self.assertEqual(newt, (10,20,30,40,50))
+
+        # Broader test of all interesting names in a template
+        with support.captured_stdout() as template:
+            T = namedtuple('T', 'x', verbose=True)
+        words = set(re.findall('[A-Za-z]+', template.getvalue()))
+        words -= set(keyword.kwlist)
+        T = namedtuple('T', words)
+        # test __new__
+        values = tuple(range(len(words)))
+        t = T(*values)
+        self.assertEqual(t, values)
+        t = T(**dict(zip(T._fields, values)))
+        self.assertEqual(t, values)
+        # test _make
+        t = T._make(values)
+        self.assertEqual(t, values)
+        # exercise __repr__
+        repr(t)
+        # test _asdict
+        self.assertEqual(t._asdict(), dict(zip(T._fields, values)))
+        # test _replace
+        t = T._make(values)
+        newvalues = tuple(v*10 for v in values)
+        newt = t._replace(**dict(zip(T._fields, newvalues)))
+        self.assertEqual(newt, newvalues)
+        # test _fields
+        self.assertEqual(T._fields, tuple(words))
+        # test __getnewargs__
+        self.assertEqual(t.__getnewargs__(), values)
+
 class ABCTestCase(unittest.TestCase):
 
     def validate_abstract_methods(self, abc, *names):
index 9dad57fc62737336babc2f05a6f1a549628f0218..7c6512e141d9db5019175b939a91ae5fb11dccc2 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -68,6 +68,9 @@ Library
 - Issue #6050: Don't fail extracting a directory from a zipfile if
   the directory already exists.
 
+- collections.namedtuple() was not working with the following field
+  names:  cls, self, tuple, itemgetter, and property.
+
 - Issue #5259: smtplib plain auth login no longer gives a traceback.  Fix
   by Musashi Tamura, tests by Marcin Bachry.