]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #19611: handle implicit parameters in inspect.signature
authorNick Coghlan <ncoghlan@gmail.com>
Sat, 4 Jun 2016 21:40:03 +0000 (14:40 -0700)
committerNick Coghlan <ncoghlan@gmail.com>
Sat, 4 Jun 2016 21:40:03 +0000 (14:40 -0700)
inspect.signature now reports the implicit ``.0`` parameters generated by
the compiler for comprehension and generator expression scopes as if they
were positional-only parameters called ``implicit0``.

Patch by Jelle Zijlstra.

Doc/library/inspect.rst
Lib/inspect.py
Lib/test/test_inspect.py
Misc/ACKS
Misc/NEWS

index 39948502659ac595075a83e503f0548ab589f4f8..3b3637967e327477e309eb2e57bb7cae166c535c 100644 (file)
@@ -625,6 +625,16 @@ function.
       The name of the parameter as a string.  The name must be a valid
       Python identifier.
 
+      .. impl-detail::
+
+         CPython generates implicit parameter names of the form ``.0`` on the
+         code objects used to implement comprehensions and generator
+         expressions.
+
+         .. versionchanged:: 3.6
+            These parameter names are exposed by this module as names like
+            ``implicit0``.
+
    .. attribute:: Parameter.default
 
       The default value for the parameter.  If the parameter has no default
index 582bb0e7fafc2c7a819c32e9588c2299b2eae313..45d21101068b559ee5422acfbe0224ea39be7457 100644 (file)
@@ -2396,6 +2396,20 @@ class Parameter:
         if not isinstance(name, str):
             raise TypeError("name must be a str, not a {!r}".format(name))
 
+        if name[0] == '.' and name[1:].isdigit():
+            # These are implicit arguments generated by comprehensions. In
+            # order to provide a friendlier interface to users, we recast
+            # their name as "implicitN" and treat them as positional-only.
+            # See issue 19611.
+            if kind != _POSITIONAL_OR_KEYWORD:
+                raise ValueError(
+                    'implicit arguments must be passed in as {}'.format(
+                        _POSITIONAL_OR_KEYWORD
+                    )
+                )
+            self._kind = _POSITIONAL_ONLY
+            name = 'implicit{}'.format(name[1:])
+
         if not name.isidentifier():
             raise ValueError('{!r} is not a valid parameter name'.format(name))
 
index 76cebcc2213a0ce545c6f780cf3bdee4f030135b..47244aeaa4943f68bbdfa86905ea13e90ef58122 100644 (file)
@@ -2903,6 +2903,10 @@ class TestParameterObject(unittest.TestCase):
                                     'is not a valid parameter name'):
             inspect.Parameter('$', kind=inspect.Parameter.VAR_KEYWORD)
 
+        with self.assertRaisesRegex(ValueError,
+                                    'is not a valid parameter name'):
+            inspect.Parameter('.a', kind=inspect.Parameter.VAR_KEYWORD)
+
         with self.assertRaisesRegex(ValueError, 'cannot have default values'):
             inspect.Parameter('a', default=42,
                               kind=inspect.Parameter.VAR_KEYWORD)
@@ -2986,6 +2990,17 @@ class TestParameterObject(unittest.TestCase):
         with self.assertRaisesRegex(TypeError, 'name must be a str'):
             inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
 
+    @cpython_only
+    def test_signature_parameter_implicit(self):
+        with self.assertRaisesRegex(ValueError,
+                                    'implicit arguments must be passed in as'):
+            inspect.Parameter('.0', kind=inspect.Parameter.POSITIONAL_ONLY)
+
+        param = inspect.Parameter(
+            '.0', kind=inspect.Parameter.POSITIONAL_OR_KEYWORD)
+        self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_ONLY)
+        self.assertEqual(param.name, 'implicit0')
+
     def test_signature_parameter_immutability(self):
         p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY)
 
@@ -3234,6 +3249,17 @@ class TestSignatureBind(unittest.TestCase):
         ba = sig.bind(args=1)
         self.assertEqual(ba.arguments, {'kwargs': {'args': 1}})
 
+    @cpython_only
+    def test_signature_bind_implicit_arg(self):
+        # Issue #19611: getcallargs should work with set comprehensions
+        def make_set():
+            return {z * z for z in range(5)}
+        setcomp_code = make_set.__code__.co_consts[1]
+        setcomp_func = types.FunctionType(setcomp_code, {})
+
+        iterator = iter(range(5))
+        self.assertEqual(self.call(setcomp_func, iterator), {0, 1, 4, 9, 16})
+
 
 class TestBoundArguments(unittest.TestCase):
     def test_signature_bound_arguments_unhashable(self):
index ee67e2737caf31ffe0bdec877194e45cf7bf8b99..7f289fba205abf06d83f13194415e27dfacbaf83 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1665,6 +1665,7 @@ Uwe Zessin
 Cheng Zhang
 Kai Zhu
 Tarek Ziadé
+Jelle Zijlstra
 Gennadiy Zlobin
 Doug Zongker
 Peter Ã…strand
index 5a1d4d56f93340aa13c1ae7c396f5e916aca1a2f..0dc317e2282fb5c8b7c403561030702c462954be 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -27,6 +27,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #19611: :mod:`inspect` now reports the implicit ``.0`` parameters
+  generated by the compiler for comprehension and generator expression scopes
+  as if they were positional-only parameters called ``implicit0``.
+  Patch by Jelle Zijlstra.
+
 - Issue #26809: Add ``__all__`` to :mod:`string`.  Patch by Emanuel Barry.
 
 - Issue #26373: subprocess.Popen.communicate now correctly ignores