summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/inspect.rst22
-rw-r--r--Doc/whatsnew/3.10.rst5
-rw-r--r--Lib/inspect.py80
-rw-r--r--Lib/test/test_inspect.py20
-rw-r--r--Misc/NEWS.d/next/Library/2020-10-06-23-59-20.bpo-41960.icQ7Xd.rst2
5 files changed, 77 insertions, 52 deletions
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index b53a942..850d601 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -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`
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``::
@@ -581,6 +581,9 @@ function.
Raises :exc:`ValueError` if no signature can be provided, and
: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
to it are positional-only. For more info, see
: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
unwrap decorated callables.)
+ .. versionadded:: 3.10
+ ``globalns`` and ``localns`` parameters.
+
.. note::
Some callables may not be introspectable in certain implementations of
Python. For example, in CPython, some built-in functions defined in
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)
@@ -668,11 +680,12 @@ function.
>>> str(new_sig)
"(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
``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`::
@@ -683,6 +696,9 @@ function.
.. versionadded:: 3.5
+ .. versionadded:: 3.10
+ ``globalns`` and ``localns`` parameters.
+
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index a6f9b0b..b5fb1e9 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -238,6 +238,11 @@ inspect
When a module does not define ``__loader__``, fall back to ``__spec__.loader``.
(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
---------
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 9150ac1..70c5ef7 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2137,9 +2137,9 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True):
return cls(parameters, return_annotation=cls.empty)
-def _get_type_hints(func):
+def _get_type_hints(func, **kwargs):
try:
- return typing.get_type_hints(func)
+ return typing.get_type_hints(func, **kwargs)
except Exception:
# First, try to use the get_type_hints to resolve
# 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)
-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."""
is_duck_function = False
@@ -2190,7 +2191,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
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__
kwdefaults = func.__kwdefaults__
@@ -2262,23 +2263,28 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
+ globalns=None,
+ localns=None,
sigcls):
"""Private helper function to get signature for arbitrary
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):
raise TypeError('{!r} is not a callable object'.format(obj))
if isinstance(obj, types.MethodType):
# In this case we skip the first parameter of the underlying
# function (usually `self` or `cls`).
- sig = _signature_from_callable(
- obj.__func__,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ sig = _get_signature_of(obj.__func__)
if skip_bound_arg:
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
# skip its first parameter (self).
# See test_signature_wrapped_bound_method for details.
- return _signature_from_callable(
- obj,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ return _get_signature_of(obj)
try:
sig = obj.__signature__
@@ -2323,11 +2325,7 @@ def _signature_from_callable(obj, *,
# (usually `self`, or `cls`) will not be passed
# automatically (as for boundmethods)
- wrapped_sig = _signature_from_callable(
- partialmethod.func,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ wrapped_sig = _get_signature_of(partialmethod.func)
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
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
# of a Python function (Cython functions, for instance), then:
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):
return _signature_from_builtin(sigcls, obj,
skip_bound_arg=skip_bound_arg)
if isinstance(obj, functools.partial):
- wrapped_sig = _signature_from_callable(
- obj.func,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)
sig = None
@@ -2368,29 +2363,17 @@ def _signature_from_callable(obj, *,
# in its metaclass
call = _signature_get_user_defined_method(type(obj), '__call__')
if call is not None:
- sig = _signature_from_callable(
- call,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ sig = _get_signature_of(call)
else:
# Now we check if the 'obj' class has a '__new__' method
new = _signature_get_user_defined_method(obj, '__new__')
if new is not None:
- sig = _signature_from_callable(
- new,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ sig = _get_signature_of(new)
else:
# Finally, we should have at least __init__ implemented
init = _signature_get_user_defined_method(obj, '__init__')
if init is not None:
- sig = _signature_from_callable(
- init,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ sig = _get_signature_of(init)
if sig is None:
# 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__')
if call is not None:
try:
- sig = _signature_from_callable(
- call,
- follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg,
- sigcls=sigcls)
+ sig = _get_signature_of(call)
except ValueError as ex:
msg = 'no signature found for {!r}'.format(obj)
raise ValueError(msg) from ex
@@ -2892,10 +2871,12 @@ class Signature:
return _signature_from_builtin(cls, func)
@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."""
return _signature_from_callable(obj, sigcls=cls,
- follow_wrapper_chains=follow_wrapped)
+ follow_wrapper_chains=follow_wrapped,
+ globalns=globalns, localns=localns)
@property
def parameters(self):
@@ -3143,9 +3124,10 @@ class Signature:
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."""
- return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
+ return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
+ globalns=globalns, localns=localns)
def _main():
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index c81d828..706fcbe 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -3250,6 +3250,26 @@ class TestSignatureObject(unittest.TestCase):
p2 = inspect.signature(lambda y, x: None).parameters
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):
def test_signature_parameter_kinds(self):
diff --git a/Misc/NEWS.d/next/Library/2020-10-06-23-59-20.bpo-41960.icQ7Xd.rst b/Misc/NEWS.d/next/Library/2020-10-06-23-59-20.bpo-41960.icQ7Xd.rst
new file mode 100644
index 0000000..f7e7199
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-10-06-23-59-20.bpo-41960.icQ7Xd.rst
@@ -0,0 +1,2 @@
+Add ``globalns`` and ``localns`` parameters to the :func:`inspect.signature`
+and :meth:`inspect.Signature.from_callable`.