]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-132493: Remove __annotations__ usage in inspect._signature_is_functionlike (#133415)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Sat, 10 May 2025 01:42:53 +0000 (18:42 -0700)
committerGitHub <noreply@github.com>
Sat, 10 May 2025 01:42:53 +0000 (18:42 -0700)
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
Lib/test/test_inspect/test_inspect.py
Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst [new file with mode: 0644]

index 52c9bb05b31f372777ac82de9f05c2fa2a17cb25..183e67fabf966ee61f774f801b678cdf1f4c8fd3 100644 (file)
@@ -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):
index bc1129827079d4bfaba6123293110a65c69a1287..1c325c0d5075a277685b1284aec2dd0f4ea2e816 100644 (file)
@@ -4997,6 +4997,37 @@ class TestSignatureObject(unittest.TestCase):
                 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), ['x'])
+        sig = inspect.signature(C, annotation_format=Format.FORWARDREF)
+        self.assertEqual(list(sig.parameters), ['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), ['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 (file)
index 0000000..ad06ee6
--- /dev/null
@@ -0,0 +1,2 @@
+Avoid accessing ``__annotations__`` unnecessarily in
+:func:`inspect.signature`.