summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2018-12-31 12:15:16 (GMT)
committerGitHub <noreply@github.com>2018-12-31 12:15:16 (GMT)
commit052b2dfdc967a8c061ff9561534e905009b88b8c (patch)
tree1b9c6c026ff687e9332116d52d2d9ec55acf1abb /Lib
parent5c117dd227e1b4c4f0a62564d8592f1ba45c91eb (diff)
downloadcpython-052b2dfdc967a8c061ff9561534e905009b88b8c.zip
cpython-052b2dfdc967a8c061ff9561534e905009b88b8c.tar.gz
cpython-052b2dfdc967a8c061ff9561534e905009b88b8c.tar.bz2
bpo-32492: Tweak _collections._tuplegetter. (GH-11367)
* Replace the docstrings cache with sys.intern(). * Improve tests. * Unify names of tp_descr_get and tp_descr_set functions.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/collections/__init__.py13
-rw-r--r--Lib/test/test_collections.py73
-rw-r--r--Lib/test/test_pydoc.py10
3 files changed, 67 insertions, 29 deletions
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index 0b74c3f..c31d7b7 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -316,8 +316,6 @@ try:
except ImportError:
_tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc)
-_nt_itemgetters = {}
-
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
"""Returns a new subclass of tuple with named fields.
@@ -456,16 +454,9 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
'_asdict': _asdict,
'__getnewargs__': __getnewargs__,
}
- cache = _nt_itemgetters
for index, name in enumerate(field_names):
- try:
- doc = cache[index]
- except KeyError:
- doc = f'Alias for field number {index}'
- cache[index] = doc
-
- tuplegetter_object = _tuplegetter(index, doc)
- class_namespace[name] = tuplegetter_object
+ doc = _sys.intern(f'Alias for field number {index}')
+ class_namespace[name] = _tuplegetter(index, doc)
result = type(typename, (tuple,), class_namespace)
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index c081594..74372d2 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -3,6 +3,7 @@
import collections
import copy
import doctest
+import inspect
import operator
import pickle
from random import choice, randrange
@@ -281,20 +282,50 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Point(1), (1, 20))
self.assertEqual(Point(), (10, 20))
+ def test_readonly(self):
+ Point = namedtuple('Point', 'x y')
+ p = Point(11, 22)
+ with self.assertRaises(AttributeError):
+ p.x = 33
+ with self.assertRaises(AttributeError):
+ del p.x
+ with self.assertRaises(TypeError):
+ p[0] = 33
+ with self.assertRaises(TypeError):
+ del p[0]
+ self.assertEqual(p.x, 11)
+ self.assertEqual(p[0], 11)
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_factory_doc_attr(self):
Point = namedtuple('Point', 'x y')
self.assertEqual(Point.__doc__, 'Point(x, y)')
+ Point.__doc__ = '2D point'
+ self.assertEqual(Point.__doc__, '2D point')
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
- def test_doc_writable(self):
+ def test_field_doc(self):
Point = namedtuple('Point', 'x y')
self.assertEqual(Point.x.__doc__, 'Alias for field number 0')
+ self.assertEqual(Point.y.__doc__, 'Alias for field number 1')
Point.x.__doc__ = 'docstring for Point.x'
self.assertEqual(Point.x.__doc__, 'docstring for Point.x')
+ # namedtuple can mutate doc of descriptors independently
+ Vector = namedtuple('Vector', 'x y')
+ self.assertEqual(Vector.x.__doc__, 'Alias for field number 0')
+ Vector.x.__doc__ = 'docstring for Vector.x'
+ self.assertEqual(Vector.x.__doc__, 'docstring for Vector.x')
+
+ @support.cpython_only
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_field_doc_reuse(self):
+ P = namedtuple('P', ['m', 'n'])
+ Q = namedtuple('Q', ['o', 'p'])
+ self.assertIs(P.m.__doc__, Q.o.__doc__)
+ self.assertIs(P.n.__doc__, Q.p.__doc__)
def test_name_fixer(self):
for spec, renamed in [
@@ -319,16 +350,18 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(p, Point(y=22, x=11))
self.assertEqual(p, Point(*(11, 22)))
self.assertEqual(p, Point(**dict(x=11, y=22)))
- self.assertRaises(TypeError, Point, 1) # too few args
- self.assertRaises(TypeError, Point, 1, 2, 3) # too many args
- self.assertRaises(TypeError, eval, 'Point(XXX=1, y=2)', locals()) # wrong keyword argument
- self.assertRaises(TypeError, eval, 'Point(x=1)', locals()) # missing keyword argument
+ self.assertRaises(TypeError, Point, 1) # too few args
+ self.assertRaises(TypeError, Point, 1, 2, 3) # too many args
+ with self.assertRaises(TypeError): # wrong keyword argument
+ Point(XXX=1, y=2)
+ with self.assertRaises(TypeError): # missing keyword argument
+ Point(x=1)
self.assertEqual(repr(p), 'Point(x=11, y=22)')
self.assertNotIn('__weakref__', dir(p))
- self.assertEqual(p, Point._make([11, 22])) # test _make classmethod
- self.assertEqual(p._fields, ('x', 'y')) # test _fields attribute
- self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method
- self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method
+ self.assertEqual(p, Point._make([11, 22])) # test _make classmethod
+ self.assertEqual(p._fields, ('x', 'y')) # test _fields attribute
+ self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method
+ self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method
try:
p._replace(x=1, error=2)
@@ -360,11 +393,15 @@ class TestNamedTuple(unittest.TestCase):
x, y = p
self.assertEqual(p, (x, y)) # unpacks like a tuple
self.assertEqual((p[0], p[1]), (11, 22)) # indexable like a tuple
- self.assertRaises(IndexError, p.__getitem__, 3)
+ with self.assertRaises(IndexError):
+ p[3]
+ self.assertEqual(p[-1], 22)
+ self.assertEqual(hash(p), hash((11, 22)))
self.assertEqual(p.x, x)
self.assertEqual(p.y, y)
- self.assertRaises(AttributeError, eval, 'p.z', locals())
+ with self.assertRaises(AttributeError):
+ p.z
def test_odd_sizes(self):
Zero = namedtuple('Zero', '')
@@ -514,13 +551,13 @@ class TestNamedTuple(unittest.TestCase):
a.w = 5
self.assertEqual(a.__dict__, {'w': 5})
- def test_namedtuple_can_mutate_doc_of_descriptors_independently(self):
- A = namedtuple('A', 'x y')
- B = namedtuple('B', 'x y')
- A.x.__doc__ = 'foo'
- B.x.__doc__ = 'bar'
- self.assertEqual(A.x.__doc__, 'foo')
- self.assertEqual(B.x.__doc__, 'bar')
+ def test_field_descriptor(self):
+ Point = namedtuple('Point', 'x y')
+ p = Point(11, 22)
+ self.assertTrue(inspect.isdatadescriptor(Point.x))
+ self.assertEqual(Point.x.__get__(p), 11)
+ self.assertRaises(AttributeError, Point.x.__set__, p, 33)
+ self.assertRaises(AttributeError, Point.x.__delete__, p)
################################################################################
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index c58a8b1..ffe80fc 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -687,6 +687,16 @@ class PydocDocTest(unittest.TestCase):
finally:
pydoc.getpager = getpager_old
+ def test_namedtuple_fields(self):
+ Person = namedtuple('Person', ['nickname', 'firstname'])
+ with captured_stdout() as help_io:
+ pydoc.help(Person)
+ helptext = help_io.getvalue()
+ self.assertIn("nickname", helptext)
+ self.assertIn("firstname", helptext)
+ self.assertIn("Alias for field number 0", helptext)
+ self.assertIn("Alias for field number 1", helptext)
+
def test_namedtuple_public_underscore(self):
NT = namedtuple('NT', ['abc', 'def'], rename=True)
with captured_stdout() as help_io: