bpo-41960: Add globalns and localns parameters to inspect.signature and Signature.from_callable (GH-22583)
This commit is contained in:
parent
6b1ac809b9
commit
eee1c7745a
|
@ -556,7 +556,7 @@ The Signature object represents the call signature of a callable object and its
|
||||||
return annotation. To retrieve a Signature object, use the :func:`signature`
|
return annotation. To retrieve a Signature object, use the :func:`signature`
|
||||||
function.
|
function.
|
||||||
|
|
||||||
.. function:: signature(callable, *, follow_wrapped=True)
|
.. function:: signature(callable, *, follow_wrapped=True, globalns=None, localns=None)
|
||||||
|
|
||||||
Return a :class:`Signature` object for the given ``callable``::
|
Return a :class:`Signature` object for the given ``callable``::
|
||||||
|
|
||||||
|
@ -581,6 +581,9 @@ function.
|
||||||
Raises :exc:`ValueError` if no signature can be provided, and
|
Raises :exc:`ValueError` if no signature can be provided, and
|
||||||
:exc:`TypeError` if that type of object is not supported.
|
:exc:`TypeError` if that type of object is not supported.
|
||||||
|
|
||||||
|
``globalns`` and ``localns`` are passed into
|
||||||
|
:func:`typing.get_type_hints` when resolving the annotations.
|
||||||
|
|
||||||
A slash(/) in the signature of a function denotes that the parameters prior
|
A slash(/) in the signature of a function denotes that the parameters prior
|
||||||
to it are positional-only. For more info, see
|
to it are positional-only. For more info, see
|
||||||
:ref:`the FAQ entry on positional-only parameters <faq-positional-only-arguments>`.
|
:ref:`the FAQ entry on positional-only parameters <faq-positional-only-arguments>`.
|
||||||
|
@ -590,12 +593,21 @@ function.
|
||||||
``callable`` specifically (``callable.__wrapped__`` will not be used to
|
``callable`` specifically (``callable.__wrapped__`` will not be used to
|
||||||
unwrap decorated callables.)
|
unwrap decorated callables.)
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
``globalns`` and ``localns`` parameters.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Some callables may not be introspectable in certain implementations of
|
Some callables may not be introspectable in certain implementations of
|
||||||
Python. For example, in CPython, some built-in functions defined in
|
Python. For example, in CPython, some built-in functions defined in
|
||||||
C provide no metadata about their arguments.
|
C provide no metadata about their arguments.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Will first try to resolve the annotations, but when it fails and
|
||||||
|
encounters with an error while that operation, the annotations will be
|
||||||
|
returned unchanged (as strings).
|
||||||
|
|
||||||
|
|
||||||
.. class:: Signature(parameters=None, *, return_annotation=Signature.empty)
|
.. class:: Signature(parameters=None, *, return_annotation=Signature.empty)
|
||||||
|
|
||||||
|
@ -668,11 +680,12 @@ function.
|
||||||
>>> str(new_sig)
|
>>> str(new_sig)
|
||||||
"(a, b) -> 'new return anno'"
|
"(a, b) -> 'new return anno'"
|
||||||
|
|
||||||
.. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True)
|
.. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globalns=None, localns=None)
|
||||||
|
|
||||||
Return a :class:`Signature` (or its subclass) object for a given callable
|
Return a :class:`Signature` (or its subclass) object for a given callable
|
||||||
``obj``. Pass ``follow_wrapped=False`` to get a signature of ``obj``
|
``obj``. Pass ``follow_wrapped=False`` to get a signature of ``obj``
|
||||||
without unwrapping its ``__wrapped__`` chain.
|
without unwrapping its ``__wrapped__`` chain. ``globalns`` and
|
||||||
|
``localns`` will be used as the namespaces when resolving annotations.
|
||||||
|
|
||||||
This method simplifies subclassing of :class:`Signature`::
|
This method simplifies subclassing of :class:`Signature`::
|
||||||
|
|
||||||
|
@ -683,6 +696,9 @@ function.
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
``globalns`` and ``localns`` parameters.
|
||||||
|
|
||||||
|
|
||||||
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
|
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,11 @@ inspect
|
||||||
When a module does not define ``__loader__``, fall back to ``__spec__.loader``.
|
When a module does not define ``__loader__``, fall back to ``__spec__.loader``.
|
||||||
(Contributed by Brett Cannon in :issue:`42133`.)
|
(Contributed by Brett Cannon in :issue:`42133`.)
|
||||||
|
|
||||||
|
Added *globalns* and *localns* parameters in :func:`~inspect.signature` and
|
||||||
|
:meth:`inspect.Signature.from_callable` to retrieve the annotations in given
|
||||||
|
local and global namespaces.
|
||||||
|
(Contributed by Batuhan Taskaya in :issue:`41960`.)
|
||||||
|
|
||||||
linecache
|
linecache
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
|
@ -2137,9 +2137,9 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True):
|
||||||
|
|
||||||
return cls(parameters, return_annotation=cls.empty)
|
return cls(parameters, return_annotation=cls.empty)
|
||||||
|
|
||||||
def _get_type_hints(func):
|
def _get_type_hints(func, **kwargs):
|
||||||
try:
|
try:
|
||||||
return typing.get_type_hints(func)
|
return typing.get_type_hints(func, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
# First, try to use the get_type_hints to resolve
|
# First, try to use the get_type_hints to resolve
|
||||||
# annotations. But for keeping the behavior intact
|
# annotations. But for keeping the behavior intact
|
||||||
|
@ -2164,7 +2164,8 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
|
||||||
return _signature_fromstr(cls, func, s, skip_bound_arg)
|
return _signature_fromstr(cls, func, s, skip_bound_arg)
|
||||||
|
|
||||||
|
|
||||||
def _signature_from_function(cls, func, skip_bound_arg=True):
|
def _signature_from_function(cls, func, skip_bound_arg=True,
|
||||||
|
globalns=None, localns=None):
|
||||||
"""Private helper: constructs Signature for the given python function."""
|
"""Private helper: constructs Signature for the given python function."""
|
||||||
|
|
||||||
is_duck_function = False
|
is_duck_function = False
|
||||||
|
@ -2190,7 +2191,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
|
||||||
positional = arg_names[:pos_count]
|
positional = arg_names[:pos_count]
|
||||||
keyword_only_count = func_code.co_kwonlyargcount
|
keyword_only_count = func_code.co_kwonlyargcount
|
||||||
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
|
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
|
||||||
annotations = _get_type_hints(func)
|
annotations = _get_type_hints(func, globalns=globalns, localns=localns)
|
||||||
|
|
||||||
defaults = func.__defaults__
|
defaults = func.__defaults__
|
||||||
kwdefaults = func.__kwdefaults__
|
kwdefaults = func.__kwdefaults__
|
||||||
|
@ -2262,23 +2263,28 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
|
||||||
def _signature_from_callable(obj, *,
|
def _signature_from_callable(obj, *,
|
||||||
follow_wrapper_chains=True,
|
follow_wrapper_chains=True,
|
||||||
skip_bound_arg=True,
|
skip_bound_arg=True,
|
||||||
|
globalns=None,
|
||||||
|
localns=None,
|
||||||
sigcls):
|
sigcls):
|
||||||
|
|
||||||
"""Private helper function to get signature for arbitrary
|
"""Private helper function to get signature for arbitrary
|
||||||
callable objects.
|
callable objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_get_signature_of = functools.partial(_signature_from_callable,
|
||||||
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
globalns=globalns,
|
||||||
|
localns=localns,
|
||||||
|
sigcls=sigcls)
|
||||||
|
|
||||||
if not callable(obj):
|
if not callable(obj):
|
||||||
raise TypeError('{!r} is not a callable object'.format(obj))
|
raise TypeError('{!r} is not a callable object'.format(obj))
|
||||||
|
|
||||||
if isinstance(obj, types.MethodType):
|
if isinstance(obj, types.MethodType):
|
||||||
# In this case we skip the first parameter of the underlying
|
# In this case we skip the first parameter of the underlying
|
||||||
# function (usually `self` or `cls`).
|
# function (usually `self` or `cls`).
|
||||||
sig = _signature_from_callable(
|
sig = _get_signature_of(obj.__func__)
|
||||||
obj.__func__,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
|
|
||||||
if skip_bound_arg:
|
if skip_bound_arg:
|
||||||
return _signature_bound_method(sig)
|
return _signature_bound_method(sig)
|
||||||
|
@ -2292,11 +2298,7 @@ def _signature_from_callable(obj, *,
|
||||||
# If the unwrapped object is a *method*, we might want to
|
# If the unwrapped object is a *method*, we might want to
|
||||||
# skip its first parameter (self).
|
# skip its first parameter (self).
|
||||||
# See test_signature_wrapped_bound_method for details.
|
# See test_signature_wrapped_bound_method for details.
|
||||||
return _signature_from_callable(
|
return _get_signature_of(obj)
|
||||||
obj,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sig = obj.__signature__
|
sig = obj.__signature__
|
||||||
|
@ -2323,11 +2325,7 @@ def _signature_from_callable(obj, *,
|
||||||
# (usually `self`, or `cls`) will not be passed
|
# (usually `self`, or `cls`) will not be passed
|
||||||
# automatically (as for boundmethods)
|
# automatically (as for boundmethods)
|
||||||
|
|
||||||
wrapped_sig = _signature_from_callable(
|
wrapped_sig = _get_signature_of(partialmethod.func)
|
||||||
partialmethod.func,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
|
|
||||||
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
|
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
|
||||||
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
|
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
|
||||||
|
@ -2346,18 +2344,15 @@ def _signature_from_callable(obj, *,
|
||||||
# If it's a pure Python function, or an object that is duck type
|
# If it's a pure Python function, or an object that is duck type
|
||||||
# of a Python function (Cython functions, for instance), then:
|
# of a Python function (Cython functions, for instance), then:
|
||||||
return _signature_from_function(sigcls, obj,
|
return _signature_from_function(sigcls, obj,
|
||||||
skip_bound_arg=skip_bound_arg)
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
globalns=globalns, localns=localns)
|
||||||
|
|
||||||
if _signature_is_builtin(obj):
|
if _signature_is_builtin(obj):
|
||||||
return _signature_from_builtin(sigcls, obj,
|
return _signature_from_builtin(sigcls, obj,
|
||||||
skip_bound_arg=skip_bound_arg)
|
skip_bound_arg=skip_bound_arg)
|
||||||
|
|
||||||
if isinstance(obj, functools.partial):
|
if isinstance(obj, functools.partial):
|
||||||
wrapped_sig = _signature_from_callable(
|
wrapped_sig = _get_signature_of(obj.func)
|
||||||
obj.func,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
return _signature_get_partial(wrapped_sig, obj)
|
return _signature_get_partial(wrapped_sig, obj)
|
||||||
|
|
||||||
sig = None
|
sig = None
|
||||||
|
@ -2368,29 +2363,17 @@ def _signature_from_callable(obj, *,
|
||||||
# in its metaclass
|
# in its metaclass
|
||||||
call = _signature_get_user_defined_method(type(obj), '__call__')
|
call = _signature_get_user_defined_method(type(obj), '__call__')
|
||||||
if call is not None:
|
if call is not None:
|
||||||
sig = _signature_from_callable(
|
sig = _get_signature_of(call)
|
||||||
call,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
else:
|
else:
|
||||||
# Now we check if the 'obj' class has a '__new__' method
|
# Now we check if the 'obj' class has a '__new__' method
|
||||||
new = _signature_get_user_defined_method(obj, '__new__')
|
new = _signature_get_user_defined_method(obj, '__new__')
|
||||||
if new is not None:
|
if new is not None:
|
||||||
sig = _signature_from_callable(
|
sig = _get_signature_of(new)
|
||||||
new,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
else:
|
else:
|
||||||
# Finally, we should have at least __init__ implemented
|
# Finally, we should have at least __init__ implemented
|
||||||
init = _signature_get_user_defined_method(obj, '__init__')
|
init = _signature_get_user_defined_method(obj, '__init__')
|
||||||
if init is not None:
|
if init is not None:
|
||||||
sig = _signature_from_callable(
|
sig = _get_signature_of(init)
|
||||||
init,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
|
|
||||||
if sig is None:
|
if sig is None:
|
||||||
# At this point we know, that `obj` is a class, with no user-
|
# At this point we know, that `obj` is a class, with no user-
|
||||||
|
@ -2436,11 +2419,7 @@ def _signature_from_callable(obj, *,
|
||||||
call = _signature_get_user_defined_method(type(obj), '__call__')
|
call = _signature_get_user_defined_method(type(obj), '__call__')
|
||||||
if call is not None:
|
if call is not None:
|
||||||
try:
|
try:
|
||||||
sig = _signature_from_callable(
|
sig = _get_signature_of(call)
|
||||||
call,
|
|
||||||
follow_wrapper_chains=follow_wrapper_chains,
|
|
||||||
skip_bound_arg=skip_bound_arg,
|
|
||||||
sigcls=sigcls)
|
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
msg = 'no signature found for {!r}'.format(obj)
|
msg = 'no signature found for {!r}'.format(obj)
|
||||||
raise ValueError(msg) from ex
|
raise ValueError(msg) from ex
|
||||||
|
@ -2892,10 +2871,12 @@ class Signature:
|
||||||
return _signature_from_builtin(cls, func)
|
return _signature_from_builtin(cls, func)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_callable(cls, obj, *, follow_wrapped=True):
|
def from_callable(cls, obj, *,
|
||||||
|
follow_wrapped=True, globalns=None, localns=None):
|
||||||
"""Constructs Signature for the given callable object."""
|
"""Constructs Signature for the given callable object."""
|
||||||
return _signature_from_callable(obj, sigcls=cls,
|
return _signature_from_callable(obj, sigcls=cls,
|
||||||
follow_wrapper_chains=follow_wrapped)
|
follow_wrapper_chains=follow_wrapped,
|
||||||
|
globalns=globalns, localns=localns)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parameters(self):
|
def parameters(self):
|
||||||
|
@ -3143,9 +3124,10 @@ class Signature:
|
||||||
return rendered
|
return rendered
|
||||||
|
|
||||||
|
|
||||||
def signature(obj, *, follow_wrapped=True):
|
def signature(obj, *, follow_wrapped=True, globalns=None, localns=None):
|
||||||
"""Get a signature object for the passed callable."""
|
"""Get a signature object for the passed callable."""
|
||||||
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
|
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
|
||||||
|
globalns=globalns, localns=localns)
|
||||||
|
|
||||||
|
|
||||||
def _main():
|
def _main():
|
||||||
|
|
|
@ -3250,6 +3250,26 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
p2 = inspect.signature(lambda y, x: None).parameters
|
p2 = inspect.signature(lambda y, x: None).parameters
|
||||||
self.assertNotEqual(p1, p2)
|
self.assertNotEqual(p1, p2)
|
||||||
|
|
||||||
|
def test_signature_annotations_with_local_namespaces(self):
|
||||||
|
class Foo: ...
|
||||||
|
def func(foo: Foo) -> int: pass
|
||||||
|
def func2(foo: Foo, bar: Bar) -> int: pass
|
||||||
|
|
||||||
|
for signature_func in (inspect.signature, inspect.Signature.from_callable):
|
||||||
|
with self.subTest(signature_func = signature_func):
|
||||||
|
sig1 = signature_func(func)
|
||||||
|
self.assertEqual(sig1.return_annotation, 'int')
|
||||||
|
self.assertEqual(sig1.parameters['foo'].annotation, 'Foo')
|
||||||
|
|
||||||
|
sig2 = signature_func(func, localns=locals())
|
||||||
|
self.assertEqual(sig2.return_annotation, int)
|
||||||
|
self.assertEqual(sig2.parameters['foo'].annotation, Foo)
|
||||||
|
|
||||||
|
sig3 = signature_func(func2, globalns={'Bar': int}, localns=locals())
|
||||||
|
self.assertEqual(sig3.return_annotation, int)
|
||||||
|
self.assertEqual(sig3.parameters['foo'].annotation, Foo)
|
||||||
|
self.assertEqual(sig3.parameters['bar'].annotation, int)
|
||||||
|
|
||||||
|
|
||||||
class TestParameterObject(unittest.TestCase):
|
class TestParameterObject(unittest.TestCase):
|
||||||
def test_signature_parameter_kinds(self):
|
def test_signature_parameter_kinds(self):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add ``globalns`` and ``localns`` parameters to the :func:`inspect.signature`
|
||||||
|
and :meth:`inspect.Signature.from_callable`.
|
Loading…
Reference in New Issue