Skip to content

Commit dc3d4ff

Browse files
eendebakptclaude
andcommitted
Make the fast Parameter constructor a classmethod and add tests
Move the helper to Parameter._from_code(). Add tests that a Signature subclass overriding _parameter_cls still gets its own Parameter class (the concern raised on gh-150823), and that parameter names of function-like objects are still validated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 78329d0 commit dc3d4ff

3 files changed

Lines changed: 58 additions & 21 deletions

File tree

Lib/inspect.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,8 +2348,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
23482348
return _signature_fromstr(cls, func, s, skip_bound_arg)
23492349

23502350
Parameter = cls._parameter_cls
2351-
if Parameter is _Parameter and not is_duck_function:
2352-
Parameter = _parameter_from_code
2351+
if Parameter is Signature._parameter_cls and not is_duck_function:
2352+
Parameter = Parameter._from_code
23532353

23542354
# Parameter information.
23552355
func_code = func.__code__
@@ -2748,6 +2748,18 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty):
27482748

27492749
self._name = name
27502750

2751+
@classmethod
2752+
def _from_code(cls, name, kind, *, default=_empty, annotation=_empty):
2753+
# Fast path for Python functions: only the name needs validation.
2754+
if iskeyword(name) or not name.isidentifier():
2755+
return cls(name, kind, default=default, annotation=annotation)
2756+
self = object.__new__(cls)
2757+
self._name = name
2758+
self._kind = kind
2759+
self._default = default
2760+
self._annotation = annotation
2761+
return self
2762+
27512763
def __reduce__(self):
27522764
return (type(self),
27532765
(self._name, self._kind),
@@ -2837,25 +2849,6 @@ def __eq__(self, other):
28372849
self._annotation == other._annotation)
28382850

28392851

2840-
_Parameter = Parameter
2841-
2842-
2843-
def _parameter_from_code(name, kind, *, default=_empty, annotation=_empty):
2844-
"""Private helper: fast Parameter construction for Python functions.
2845-
2846-
The kind and default are taken from the function itself and are
2847-
known to be valid, so only the name has to be checked.
2848-
"""
2849-
if iskeyword(name) or not name.isidentifier():
2850-
return _Parameter(name, kind, default=default, annotation=annotation)
2851-
self = object.__new__(_Parameter)
2852-
self._name = name
2853-
self._kind = kind
2854-
self._default = default
2855-
self._annotation = annotation
2856-
return self
2857-
2858-
28592852
class BoundArguments:
28602853
"""Result of `Signature.bind` call. Holds the mapping of arguments
28612854
to the function's parameters.

Lib/test/test_inspect/test_inspect.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,48 @@ def __init__(self, marker):
35563556

35573557
self.assertEqual(str(inspect.signature(funclike)), '(marker)')
35583558

3559+
@cpython_only
3560+
def test_signature_functionlike_invalid_names(self):
3561+
# The code object of a function-like object is not guaranteed
3562+
# to have valid parameter names, so they must be validated.
3563+
def func(a, b):
3564+
pass
3565+
3566+
class funclike:
3567+
__name__ = func.__name__
3568+
__code__ = func.__code__.replace(co_varnames=('a', '$b'))
3569+
__annotations__ = {}
3570+
__defaults__ = None
3571+
__kwdefaults__ = None
3572+
3573+
def __call__(self, *args):
3574+
pass
3575+
3576+
with self.assertRaisesRegex(ValueError,
3577+
'is not a valid parameter name'):
3578+
inspect.signature(funclike())
3579+
3580+
def test_signature_parameter_cls_subclass(self):
3581+
# A Signature subclass can override _parameter_cls with a
3582+
# Parameter subclass that has its own constructor.
3583+
class MyParameter(inspect.Parameter):
3584+
def __init__(self, *args, **kwargs):
3585+
super().__init__(*args, **kwargs)
3586+
self.extra = 'spam'
3587+
3588+
class MySignature(inspect.Signature):
3589+
_parameter_cls = MyParameter
3590+
3591+
def f(a, /, b=1, *args, c, d=2, **kwargs):
3592+
pass
3593+
3594+
sig = MySignature.from_callable(f)
3595+
self.assertEqual(len(sig.parameters), 6)
3596+
for param in sig.parameters.values():
3597+
self.assertIs(type(param), MyParameter)
3598+
self.assertEqual(param.extra, 'spam')
3599+
self.assertEqual(sig, inspect.signature(f))
3600+
35593601
def test_signature_on_method(self):
35603602
class Test:
35613603
def __init__(*args):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :func:`inspect.signature` for Python functions by skipping
2+
redundant validation when creating :class:`inspect.Parameter` objects.

0 commit comments

Comments
 (0)