Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2393dca

Browse files
committed
inspect.signature: Use '/' to separate positional-only parameters from
the rest in Signature.__str__. #20356
1 parent ea2d66e commit 2393dca

4 files changed

Lines changed: 63 additions & 42 deletions

File tree

Doc/library/inspect.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,9 +510,8 @@ function.
510510

511511
.. attribute:: Parameter.name
512512

513-
The name of the parameter as a string. Must be a valid python identifier
514-
name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have
515-
it set to ``None``).
513+
The name of the parameter as a string. The name must be a valid
514+
Python identifier.
516515

517516
.. attribute:: Parameter.default
518517

@@ -596,6 +595,10 @@ function.
596595
>>> str(param.replace(default=Parameter.empty, annotation='spam'))
597596
"foo:'spam'"
598597

598+
.. versionchanged:: 3.4
599+
In Python 3.3 Parameter objects were allowed to have ``name`` set
600+
to ``None`` if their ``kind`` was set to ``POSITIONAL_ONLY``.
601+
This is no longer permitted.
599602

600603
.. class:: BoundArguments
601604

Doc/whatsnew/3.4.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,9 @@ removed:
14881488
* Support for loading the deprecated ``TYPE_INT64`` has been removed from
14891489
:mod:`marshal`. (Contributed by Dan Riti in :issue:`15480`.)
14901490

1491+
* :class:`inspect.Signature`: positional-only parameters are now required
1492+
to have a valid name.
1493+
14911494

14921495
Code Cleanups
14931496
-------------

Lib/inspect.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,17 +1629,16 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty,
16291629
self._default = default
16301630
self._annotation = annotation
16311631

1632-
if name is None:
1633-
if kind != _POSITIONAL_ONLY:
1634-
raise ValueError("None is not a valid name for a "
1635-
"non-positional-only parameter")
1636-
self._name = name
1637-
else:
1638-
name = str(name)
1639-
if kind != _POSITIONAL_ONLY and not name.isidentifier():
1640-
msg = '{!r} is not a valid parameter name'.format(name)
1641-
raise ValueError(msg)
1642-
self._name = name
1632+
if name is _empty:
1633+
raise ValueError('name is a required attribute for Parameter')
1634+
1635+
if not isinstance(name, str):
1636+
raise TypeError("name must be a str, not a {!r}".format(name))
1637+
1638+
if not name.isidentifier():
1639+
raise ValueError('{!r} is not a valid parameter name'.format(name))
1640+
1641+
self._name = name
16431642

16441643
self._partial_kwarg = _partial_kwarg
16451644

@@ -1683,12 +1682,7 @@ def replace(self, *, name=_void, kind=_void, annotation=_void,
16831682

16841683
def __str__(self):
16851684
kind = self.kind
1686-
16871685
formatted = self._name
1688-
if kind == _POSITIONAL_ONLY:
1689-
if formatted is None:
1690-
formatted = ''
1691-
formatted = '<{}>'.format(formatted)
16921686

16931687
# Add annotation and default value
16941688
if self._annotation is not _empty:
@@ -1858,21 +1852,19 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
18581852

18591853
for idx, param in enumerate(parameters):
18601854
kind = param.kind
1855+
name = param.name
1856+
18611857
if kind < top_kind:
18621858
msg = 'wrong parameter order: {} before {}'
1863-
msg = msg.format(top_kind, param.kind)
1859+
msg = msg.format(top_kind, kind)
18641860
raise ValueError(msg)
18651861
else:
18661862
top_kind = kind
18671863

1868-
name = param.name
1869-
if name is None:
1870-
name = str(idx)
1871-
param = param.replace(name=name)
1872-
18731864
if name in params:
18741865
msg = 'duplicate parameter name: {!r}'.format(name)
18751866
raise ValueError(msg)
1867+
18761868
params[name] = param
18771869
else:
18781870
params = OrderedDict(((param.name, param)
@@ -2292,11 +2284,21 @@ def bind_partial(__bind_self, *args, **kwargs):
22922284

22932285
def __str__(self):
22942286
result = []
2287+
render_pos_only_separator = False
22952288
render_kw_only_separator = True
2296-
for idx, param in enumerate(self.parameters.values()):
2289+
for param in self.parameters.values():
22972290
formatted = str(param)
22982291

22992292
kind = param.kind
2293+
2294+
if kind == _POSITIONAL_ONLY:
2295+
render_pos_only_separator = True
2296+
elif render_pos_only_separator:
2297+
# It's not a positional-only parameter, and the flag
2298+
# is set to 'True' (there were pos-only params before.)
2299+
result.append('/')
2300+
render_pos_only_separator = False
2301+
23002302
if kind == _VAR_POSITIONAL:
23012303
# OK, we have an '*args'-like parameter, so we won't need
23022304
# a '*' to separate keyword-only arguments
@@ -2312,6 +2314,11 @@ def __str__(self):
23122314

23132315
result.append(formatted)
23142316

2317+
if render_pos_only_separator:
2318+
# There were only positional-only parameters, hence the
2319+
# flag was not reset to 'False'
2320+
result.append('/')
2321+
23152322
rendered = '({})'.format(', '.join(result))
23162323

23172324
if self.return_annotation is not _empty:

Lib/test/test_inspect.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ def foo():
21222122

21232123
def test_signature_str_positional_only(self):
21242124
P = inspect.Parameter
2125+
S = inspect.Signature
21252126

21262127
def test(a_po, *, b, **kwargs):
21272128
return a_po, kwargs
@@ -2132,14 +2133,20 @@ def test(a_po, *, b, **kwargs):
21322133
test.__signature__ = sig.replace(parameters=new_params)
21332134

21342135
self.assertEqual(str(inspect.signature(test)),
2135-
'(<a_po>, *, b, **kwargs)')
2136+
'(a_po, /, *, b, **kwargs)')
21362137

2137-
sig = inspect.signature(test)
2138-
new_params = list(sig.parameters.values())
2139-
new_params[0] = new_params[0].replace(name=None)
2140-
test.__signature__ = sig.replace(parameters=new_params)
2141-
self.assertEqual(str(inspect.signature(test)),
2142-
'(<0>, *, b, **kwargs)')
2138+
self.assertEqual(str(S(parameters=[P('foo', P.POSITIONAL_ONLY)])),
2139+
'(foo, /)')
2140+
2141+
self.assertEqual(str(S(parameters=[
2142+
P('foo', P.POSITIONAL_ONLY),
2143+
P('bar', P.VAR_KEYWORD)])),
2144+
'(foo, /, **bar)')
2145+
2146+
self.assertEqual(str(S(parameters=[
2147+
P('foo', P.POSITIONAL_ONLY),
2148+
P('bar', P.VAR_POSITIONAL)])),
2149+
'(foo, /, *bar)')
21432150

21442151
def test_signature_replace_anno(self):
21452152
def test() -> 42:
@@ -2178,10 +2185,13 @@ def test_signature_parameter_object(self):
21782185
with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
21792186
inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD)
21802187

2181-
with self.assertRaisesRegex(ValueError,
2182-
'non-positional-only parameter'):
2188+
with self.assertRaisesRegex(TypeError, 'name must be a str'):
21832189
inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD)
21842190

2191+
with self.assertRaisesRegex(ValueError,
2192+
'is not a valid parameter name'):
2193+
inspect.Parameter('$', kind=inspect.Parameter.VAR_KEYWORD)
2194+
21852195
with self.assertRaisesRegex(ValueError, 'cannot have default values'):
21862196
inspect.Parameter('a', default=42,
21872197
kind=inspect.Parameter.VAR_KEYWORD)
@@ -2230,7 +2240,8 @@ def test_signature_parameter_replace(self):
22302240
self.assertEqual(p2.name, 'bar')
22312241
self.assertNotEqual(p2, p)
22322242

2233-
with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
2243+
with self.assertRaisesRegex(ValueError,
2244+
'name is a required attribute'):
22342245
p2 = p2.replace(name=p2.empty)
22352246

22362247
p2 = p2.replace(name='foo', default=None)
@@ -2252,14 +2263,11 @@ def test_signature_parameter_replace(self):
22522263
self.assertEqual(p2, p)
22532264

22542265
def test_signature_parameter_positional_only(self):
2255-
p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
2256-
self.assertEqual(str(p), '<>')
2257-
2258-
p = p.replace(name='1')
2259-
self.assertEqual(str(p), '<1>')
2266+
with self.assertRaisesRegex(TypeError, 'name must be a str'):
2267+
inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
22602268

22612269
def test_signature_parameter_immutability(self):
2262-
p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
2270+
p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY)
22632271

22642272
with self.assertRaises(AttributeError):
22652273
p.foo = 'bar'

0 commit comments

Comments
 (0)