Skip to content

Commit eee1c77

Browse files
authored
bpo-41960: Add globalns and localns parameters to inspect.signature and Signature.from_callable (GH-22583)
1 parent 6b1ac80 commit eee1c77

File tree

5 files changed

+77
-52
lines changed

5 files changed

+77
-52
lines changed

Doc/library/inspect.rst

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ The Signature object represents the call signature of a callable object and its
556556
return annotation. To retrieve a Signature object, use the :func:`signature`
557557
function.
558558

559-
.. function:: signature(callable, *, follow_wrapped=True)
559+
.. function:: signature(callable, *, follow_wrapped=True, globalns=None, localns=None)
560560

561561
Return a :class:`Signature` object for the given ``callable``::
562562

@@ -581,6 +581,9 @@ function.
581581
Raises :exc:`ValueError` if no signature can be provided, and
582582
:exc:`TypeError` if that type of object is not supported.
583583

584+
``globalns`` and ``localns`` are passed into
585+
:func:`typing.get_type_hints` when resolving the annotations.
586+
584587
A slash(/) in the signature of a function denotes that the parameters prior
585588
to it are positional-only. For more info, see
586589
:ref:`the FAQ entry on positional-only parameters <faq-positional-only-arguments>`.
@@ -590,12 +593,21 @@ function.
590593
``callable`` specifically (``callable.__wrapped__`` will not be used to
591594
unwrap decorated callables.)
592595

596+
.. versionadded:: 3.10
597+
``globalns`` and ``localns`` parameters.
598+
593599
.. note::
594600

595601
Some callables may not be introspectable in certain implementations of
596602
Python. For example, in CPython, some built-in functions defined in
597603
C provide no metadata about their arguments.
598604

605+
.. note::
606+
607+
Will first try to resolve the annotations, but when it fails and
608+
encounters with an error while that operation, the annotations will be
609+
returned unchanged (as strings).
610+
599611

600612
.. class:: Signature(parameters=None, *, return_annotation=Signature.empty)
601613

@@ -668,11 +680,12 @@ function.
668680
>>> str(new_sig)
669681
"(a, b) -> 'new return anno'"
670682

671-
.. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True)
683+
.. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globalns=None, localns=None)
672684

673685
Return a :class:`Signature` (or its subclass) object for a given callable
674686
``obj``. Pass ``follow_wrapped=False`` to get a signature of ``obj``
675-
without unwrapping its ``__wrapped__`` chain.
687+
without unwrapping its ``__wrapped__`` chain. ``globalns`` and
688+
``localns`` will be used as the namespaces when resolving annotations.
676689

677690
This method simplifies subclassing of :class:`Signature`::
678691

@@ -683,6 +696,9 @@ function.
683696

684697
.. versionadded:: 3.5
685698

699+
.. versionadded:: 3.10
700+
``globalns`` and ``localns`` parameters.
701+
686702

687703
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
688704

Doc/whatsnew/3.10.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ inspect
238238
When a module does not define ``__loader__``, fall back to ``__spec__.loader``.
239239
(Contributed by Brett Cannon in :issue:`42133`.)
240240

241+
Added *globalns* and *localns* parameters in :func:`~inspect.signature` and
242+
:meth:`inspect.Signature.from_callable` to retrieve the annotations in given
243+
local and global namespaces.
244+
(Contributed by Batuhan Taskaya in :issue:`41960`.)
245+
241246
linecache
242247
---------
243248

Lib/inspect.py

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2137,9 +2137,9 @@ def p(name_node, default_node, default=empty):
21372137

21382138
return cls(parameters, return_annotation=cls.empty)
21392139

2140-
def _get_type_hints(func):
2140+
def _get_type_hints(func, **kwargs):
21412141
try:
2142-
return typing.get_type_hints(func)
2142+
return typing.get_type_hints(func, **kwargs)
21432143
except Exception:
21442144
# First, try to use the get_type_hints to resolve
21452145
# annotations. But for keeping the behavior intact
@@ -2164,7 +2164,8 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
21642164
return _signature_fromstr(cls, func, s, skip_bound_arg)
21652165

21662166

2167-
def _signature_from_function(cls, func, skip_bound_arg=True):
2167+
def _signature_from_function(cls, func, skip_bound_arg=True,
2168+
globalns=None, localns=None):
21682169
"""Private helper: constructs Signature for the given python function."""
21692170

21702171
is_duck_function = False
@@ -2190,7 +2191,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
21902191
positional = arg_names[:pos_count]
21912192
keyword_only_count = func_code.co_kwonlyargcount
21922193
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
2193-
annotations = _get_type_hints(func)
2194+
annotations = _get_type_hints(func, globalns=globalns, localns=localns)
21942195

21952196
defaults = func.__defaults__
21962197
kwdefaults = func.__kwdefaults__
@@ -2262,23 +2263,28 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
22622263
def _signature_from_callable(obj, *,
22632264
follow_wrapper_chains=True,
22642265
skip_bound_arg=True,
2266+
globalns=None,
2267+
localns=None,
22652268
sigcls):
22662269

22672270
"""Private helper function to get signature for arbitrary
22682271
callable objects.
22692272
"""
22702273

2274+
_get_signature_of = functools.partial(_signature_from_callable,
2275+
follow_wrapper_chains=follow_wrapper_chains,
2276+
skip_bound_arg=skip_bound_arg,
2277+
globalns=globalns,
2278+
localns=localns,
2279+
sigcls=sigcls)
2280+
22712281
if not callable(obj):
22722282
raise TypeError('{!r} is not a callable object'.format(obj))
22732283

22742284
if isinstance(obj, types.MethodType):
22752285
# In this case we skip the first parameter of the underlying
22762286
# function (usually `self` or `cls`).
2277-
sig = _signature_from_callable(
2278-
obj.__func__,
2279-
follow_wrapper_chains=follow_wrapper_chains,
2280-
skip_bound_arg=skip_bound_arg,
2281-
sigcls=sigcls)
2287+
sig = _get_signature_of(obj.__func__)
22822288

22832289
if skip_bound_arg:
22842290
return _signature_bound_method(sig)
@@ -2292,11 +2298,7 @@ def _signature_from_callable(obj, *,
22922298
# If the unwrapped object is a *method*, we might want to
22932299
# skip its first parameter (self).
22942300
# See test_signature_wrapped_bound_method for details.
2295-
return _signature_from_callable(
2296-
obj,
2297-
follow_wrapper_chains=follow_wrapper_chains,
2298-
skip_bound_arg=skip_bound_arg,
2299-
sigcls=sigcls)
2301+
return _get_signature_of(obj)
23002302

23012303
try:
23022304
sig = obj.__signature__
@@ -2323,11 +2325,7 @@ def _signature_from_callable(obj, *,
23232325
# (usually `self`, or `cls`) will not be passed
23242326
# automatically (as for boundmethods)
23252327

2326-
wrapped_sig = _signature_from_callable(
2327-
partialmethod.func,
2328-
follow_wrapper_chains=follow_wrapper_chains,
2329-
skip_bound_arg=skip_bound_arg,
2330-
sigcls=sigcls)
2328+
wrapped_sig = _get_signature_of(partialmethod.func)
23312329

23322330
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
23332331
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
@@ -2346,18 +2344,15 @@ def _signature_from_callable(obj, *,
23462344
# If it's a pure Python function, or an object that is duck type
23472345
# of a Python function (Cython functions, for instance), then:
23482346
return _signature_from_function(sigcls, obj,
2349-
skip_bound_arg=skip_bound_arg)
2347+
skip_bound_arg=skip_bound_arg,
2348+
globalns=globalns, localns=localns)
23502349

23512350
if _signature_is_builtin(obj):
23522351
return _signature_from_builtin(sigcls, obj,
23532352
skip_bound_arg=skip_bound_arg)
23542353

23552354
if isinstance(obj, functools.partial):
2356-
wrapped_sig = _signature_from_callable(
2357-
obj.func,
2358-
follow_wrapper_chains=follow_wrapper_chains,
2359-
skip_bound_arg=skip_bound_arg,
2360-
sigcls=sigcls)
2355+
wrapped_sig = _get_signature_of(obj.func)
23612356
return _signature_get_partial(wrapped_sig, obj)
23622357

23632358
sig = None
@@ -2368,29 +2363,17 @@ def _signature_from_callable(obj, *,
23682363
# in its metaclass
23692364
call = _signature_get_user_defined_method(type(obj), '__call__')
23702365
if call is not None:
2371-
sig = _signature_from_callable(
2372-
call,
2373-
follow_wrapper_chains=follow_wrapper_chains,
2374-
skip_bound_arg=skip_bound_arg,
2375-
sigcls=sigcls)
2366+
sig = _get_signature_of(call)
23762367
else:
23772368
# Now we check if the 'obj' class has a '__new__' method
23782369
new = _signature_get_user_defined_method(obj, '__new__')
23792370
if new is not None:
2380-
sig = _signature_from_callable(
2381-
new,
2382-
follow_wrapper_chains=follow_wrapper_chains,
2383-
skip_bound_arg=skip_bound_arg,
2384-
sigcls=sigcls)
2371+
sig = _get_signature_of(new)
23852372
else:
23862373
# Finally, we should have at least __init__ implemented
23872374
init = _signature_get_user_defined_method(obj, '__init__')
23882375
if init is not None:
2389-
sig = _signature_from_callable(
2390-
init,
2391-
follow_wrapper_chains=follow_wrapper_chains,
2392-
skip_bound_arg=skip_bound_arg,
2393-
sigcls=sigcls)
2376+
sig = _get_signature_of(init)
23942377

23952378
if sig is None:
23962379
# At this point we know, that `obj` is a class, with no user-
@@ -2436,11 +2419,7 @@ def _signature_from_callable(obj, *,
24362419
call = _signature_get_user_defined_method(type(obj), '__call__')
24372420
if call is not None:
24382421
try:
2439-
sig = _signature_from_callable(
2440-
call,
2441-
follow_wrapper_chains=follow_wrapper_chains,
2442-
skip_bound_arg=skip_bound_arg,
2443-
sigcls=sigcls)
2422+
sig = _get_signature_of(call)
24442423
except ValueError as ex:
24452424
msg = 'no signature found for {!r}'.format(obj)
24462425
raise ValueError(msg) from ex
@@ -2892,10 +2871,12 @@ def from_builtin(cls, func):
28922871
return _signature_from_builtin(cls, func)
28932872

28942873
@classmethod
2895-
def from_callable(cls, obj, *, follow_wrapped=True):
2874+
def from_callable(cls, obj, *,
2875+
follow_wrapped=True, globalns=None, localns=None):
28962876
"""Constructs Signature for the given callable object."""
28972877
return _signature_from_callable(obj, sigcls=cls,
2898-
follow_wrapper_chains=follow_wrapped)
2878+
follow_wrapper_chains=follow_wrapped,
2879+
globalns=globalns, localns=localns)
28992880

29002881
@property
29012882
def parameters(self):
@@ -3143,9 +3124,10 @@ def __str__(self):
31433124
return rendered
31443125

31453126

3146-
def signature(obj, *, follow_wrapped=True):
3127+
def signature(obj, *, follow_wrapped=True, globalns=None, localns=None):
31473128
"""Get a signature object for the passed callable."""
3148-
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
3129+
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
3130+
globalns=globalns, localns=localns)
31493131

31503132

31513133
def _main():

Lib/test/test_inspect.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3250,6 +3250,26 @@ def test_signater_parameters_is_ordered(self):
32503250
p2 = inspect.signature(lambda y, x: None).parameters
32513251
self.assertNotEqual(p1, p2)
32523252

3253+
def test_signature_annotations_with_local_namespaces(self):
3254+
class Foo: ...
3255+
def func(foo: Foo) -> int: pass
3256+
def func2(foo: Foo, bar: Bar) -> int: pass
3257+
3258+
for signature_func in (inspect.signature, inspect.Signature.from_callable):
3259+
with self.subTest(signature_func = signature_func):
3260+
sig1 = signature_func(func)
3261+
self.assertEqual(sig1.return_annotation, 'int')
3262+
self.assertEqual(sig1.parameters['foo'].annotation, 'Foo')
3263+
3264+
sig2 = signature_func(func, localns=locals())
3265+
self.assertEqual(sig2.return_annotation, int)
3266+
self.assertEqual(sig2.parameters['foo'].annotation, Foo)
3267+
3268+
sig3 = signature_func(func2, globalns={'Bar': int}, localns=locals())
3269+
self.assertEqual(sig3.return_annotation, int)
3270+
self.assertEqual(sig3.parameters['foo'].annotation, Foo)
3271+
self.assertEqual(sig3.parameters['bar'].annotation, int)
3272+
32533273

32543274
class TestParameterObject(unittest.TestCase):
32553275
def test_signature_parameter_kinds(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``globalns`` and ``localns`` parameters to the :func:`inspect.signature`
2+
and :meth:`inspect.Signature.from_callable`.

0 commit comments

Comments
 (0)