From 22dbf9fab874e8b5d5cae540d182c575af5d4835 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 4 May 2025 17:05:03 -0700 Subject: [PATCH 1/4] gh-132493: Remove __annotations__ usage in inspect._signature_is_functionlike This check is potentially problematic because it could force evaluation of annotations unnecessarily. This doesn't trigger for builtin objects (functions, classes, or modules) with annotations, but it could trigger for third-party objects. The check was not particularly useful anyway, because it succeeds if ``__annotations__`` is a dict or None, so the only thing this did was guard against objects that have an ``__annotations__`` attribute that is of some other type. That doesn't seem particularly useful, so I just removed the check. --- Lib/inspect.py | 4 +-- Lib/test/test_inspect/test_inspect.py | 31 +++++++++++++++++++ ...-05-04-17-04-55.gh-issue-132493.huirKi.rst | 2 ++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index 9592559ba6dcaa..ed961cbb7fdaa8 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2074,13 +2074,11 @@ def _signature_is_functionlike(obj): code = getattr(obj, '__code__', None) defaults = getattr(obj, '__defaults__', _void) # Important to use _void ... kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here - annotations = getattr(obj, '__annotations__', None) return (isinstance(code, types.CodeType) and isinstance(name, str) and (defaults is None or isinstance(defaults, tuple)) and - (kwdefaults is None or isinstance(kwdefaults, dict)) and - (isinstance(annotations, (dict)) or annotations is None) ) + (kwdefaults is None or isinstance(kwdefaults, dict))) def _signature_strip_non_python_syntax(signature): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index c9b37fcd8f6327..8aba4723b0b60a 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4997,6 +4997,37 @@ def test_signature_annotation_format(self): with self.assertRaisesRegex(NameError, "undefined"): signature_func(ida.f) + def test_signature_deferred_annotations(self): + def f(x: undef): + pass + + class C: + x: undef + + def __init__(self, x: undef): + self.x = x + + sig = inspect.signature(f, annotation_format=Format.FORWARDREF) + self.assertEqual(list(sig.parameters.keys()), ['x']) + sig = inspect.signature(C, annotation_format=Format.FORWARDREF) + self.assertEqual(list(sig.parameters.keys()), ['x']) + + class CallableWrapper: + def __init__(self, func): + self.func = func + self.__annotate__ = func.__annotate__ + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + @property + def __annotations__(self): + return self.__annotate__(Format.VALUE) + + cw = CallableWrapper(f) + sig = inspect.signature(cw, annotation_format=Format.FORWARDREF) + self.assertEqual(list(sig.parameters.keys()), ['args', 'kwargs']) + def test_signature_none_annotation(self): class funclike: # Has to be callable, and have correct diff --git a/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst b/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst new file mode 100644 index 00000000000000..ad06ee6b7a2757 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst @@ -0,0 +1,2 @@ +Avoid accessing ``__annotations__`` unnecessarily in +:func:`inspect.signature`. From 8e3c3a921e007b10deed513e04bbe1f2470d88f7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 9 May 2025 06:46:41 -0700 Subject: [PATCH 2/4] Update Lib/test/test_inspect/test_inspect.py --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8aba4723b0b60a..ea37a486c7df09 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5008,7 +5008,7 @@ def __init__(self, x: undef): self.x = x sig = inspect.signature(f, annotation_format=Format.FORWARDREF) - self.assertEqual(list(sig.parameters.keys()), ['x']) + self.assertEqual(list(sig.parameters), ['x']) sig = inspect.signature(C, annotation_format=Format.FORWARDREF) self.assertEqual(list(sig.parameters.keys()), ['x']) From 197b00380555e1a4f9ac911fe0d402ae624a38e0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 9 May 2025 08:20:52 -0700 Subject: [PATCH 3/4] two more --- Lib/test/test_inspect/test_inspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index ea37a486c7df09..fa94b201efe75d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5010,7 +5010,7 @@ def __init__(self, x: undef): sig = inspect.signature(f, annotation_format=Format.FORWARDREF) self.assertEqual(list(sig.parameters), ['x']) sig = inspect.signature(C, annotation_format=Format.FORWARDREF) - self.assertEqual(list(sig.parameters.keys()), ['x']) + self.assertEqual(list(sig.parameters), ['x']) class CallableWrapper: def __init__(self, func): @@ -5026,7 +5026,7 @@ def __annotations__(self): cw = CallableWrapper(f) sig = inspect.signature(cw, annotation_format=Format.FORWARDREF) - self.assertEqual(list(sig.parameters.keys()), ['args', 'kwargs']) + self.assertEqual(list(sig), ['args', 'kwargs']) def test_signature_none_annotation(self): class funclike: From 43ec64e9501a73533ed9177f23f30f60920ec67f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 9 May 2025 08:59:36 -0700 Subject: [PATCH 4/4] Update test_inspect.py --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index fa94b201efe75d..f32395127d3ad0 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5026,7 +5026,7 @@ def __annotations__(self): cw = CallableWrapper(f) sig = inspect.signature(cw, annotation_format=Format.FORWARDREF) - self.assertEqual(list(sig), ['args', 'kwargs']) + self.assertEqual(list(sig.parameters), ['args', 'kwargs']) def test_signature_none_annotation(self): class funclike: