gh-132493: Remove __annotations__ usage in inspect._signature_is_functionlike (#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.
This commit is contained in:
Jelle Zijlstra 2025-05-09 18:42:53 -07:00 committed by GitHub
parent 1978904a2f
commit cb6596c6aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 34 additions and 3 deletions

View 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):

View 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

View file

@ -0,0 +1,2 @@
Avoid accessing ``__annotations__`` unnecessarily in
:func:`inspect.signature`.