diff -r c2d3d8c3e0bf Doc/library/inspect.rst --- a/Doc/library/inspect.rst Thu Jun 02 19:38:20 2016 -0400 +++ b/Doc/library/inspect.rst Fri Jun 03 09:17:33 2016 -0700 @@ -625,6 +625,16 @@ 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 diff -r c2d3d8c3e0bf Lib/inspect.py --- a/Lib/inspect.py Thu Jun 02 19:38:20 2016 -0400 +++ b/Lib/inspect.py Fri Jun 03 09:17:33 2016 -0700 @@ -2396,6 +2396,20 @@ 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)) diff -r c2d3d8c3e0bf Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Thu Jun 02 19:38:20 2016 -0400 +++ b/Lib/test/test_inspect.py Fri Jun 03 09:17:33 2016 -0700 @@ -2903,6 +2903,10 @@ '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,16 @@ with self.assertRaisesRegex(TypeError, 'name must be a str'): inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_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 +3248,16 @@ ba = sig.bind(args=1) self.assertEqual(ba.arguments, {'kwargs': {'args': 1}}) + 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):