summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2025-05-26 08:22:14 (GMT)
committerGitHub <noreply@github.com>2025-05-26 08:22:14 (GMT)
commitdc441efbd00b8dd2f26d014fb67895d381a8f602 (patch)
tree7a85fa2261565c097da1f280c5ccdfeb9f1348b4
parent1ce68f6297560c3d22169f0280a516b79375e947 (diff)
downloadcpython-dc441efbd00b8dd2f26d014fb67895d381a8f602.zip
cpython-dc441efbd00b8dd2f26d014fb67895d381a8f602.tar.gz
cpython-dc441efbd00b8dd2f26d014fb67895d381a8f602.tar.bz2
[3.14] gh-132493: Remove __annotations__ usage in inspect._signature_is_functionlike (GH-133415) (#133796)
gh-132493: Remove __annotations__ usage in inspect._signature_is_functionlike (GH-133415) 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. (cherry picked from commit cb6596c6aa30c8c3213c8746d5590e6e2757a6c1) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
-rw-r--r--Lib/inspect.py4
-rw-r--r--Lib/test/test_inspect/test_inspect.py31
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst2
3 files changed, 34 insertions, 3 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 52c9bb0..183e67f 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 57dc7a2..44f7a54 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -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
index 0000000..ad06ee6
--- /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`.