return annotation. To retrieve a Signature object, use the :func:`signature`
function.
-.. function:: signature(callable, *, follow_wrapped=True, globalns=None, localns=None)
+.. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False)
Return a :class:`Signature` object for the given ``callable``::
Accepts a wide range of Python callables, from plain functions and classes to
:func:`functools.partial` objects.
- Raises :exc:`ValueError` if no signature can be provided, and
- :exc:`TypeError` if that type of object is not supported.
+ For objects defined in modules using stringized annotations
+ (``from __future__ import annotations``), :func:`signature` will
+ attempt to automatically un-stringize the annotations using
+ :func:`inspect.get_annotations()`. The
+ ``global``, ``locals``, and ``eval_str`` parameters are passed
+ into :func:`inspect.get_annotations()` when resolving the
+ annotations; see the documentation for :func:`inspect.get_annotations()`
+ for instructions on how to use these parameters.
- ``globalns`` and ``localns`` are passed into
- :func:`typing.get_type_hints` when resolving the annotations.
+ Raises :exc:`ValueError` if no signature can be provided, and
+ :exc:`TypeError` if that type of object is not supported. Also,
+ if the annotations are stringized, and ``eval_str`` is not false,
+ the ``eval()`` call(s) to un-stringize the annotations could
+ potentially raise any kind of exception.
A slash(/) in the signature of a function denotes that the parameters prior
to it are positional-only. For more info, see
unwrap decorated callables.)
.. versionadded:: 3.10
- ``globalns`` and ``localns`` parameters.
+ ``globals``, ``locals``, and ``eval_str`` parameters.
.. note::
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)
.. versionadded:: 3.4
+.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False)
+
+ Compute the annotations dict for an object.
+
+ ``obj`` may be a callable, class, or module.
+ Passing in an object of any other type raises :exc:`TypeError`.
+
+ Returns a dict. ``get_annotations()`` returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This function handles several details for you:
+
+ * If ``eval_str`` is true, values of type ``str`` will
+ be un-stringized using :func:`eval()`. This is intended
+ for use with stringized annotations
+ (``from __future__ import annotations``).
+ * If ``obj`` doesn't have an annotations dict, returns an
+ empty dict. (Functions and methods always have an
+ annotations dict; classes, modules, and other types of
+ callables may not.)
+ * Ignores inherited annotations on classes. If a class
+ doesn't have its own annotations dict, returns an empty dict.
+ * All accesses to object members and dict values are done
+ using ``getattr()`` and ``dict.get()`` for safety.
+ * Always, always, always returns a freshly-created dict.
+
+ ``eval_str`` controls whether or not values of type ``str`` are replaced
+ with the result of calling :func:`eval()` on those values:
+
+ * If eval_str is true, :func:`eval()` is called on values of type ``str``.
+ * If eval_str is false (the default), values of type ``str`` are unchanged.
+
+ ``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
+ for :func:`eval()` for more information. If ``globals`` or ``locals``
+ is ``None``, this function may replace that value with a context-specific
+ default, contingent on ``type(obj)``:
+
+ * If ``obj`` is a module, ``globals`` defaults to ``obj.__dict__``.
+ * If ``obj`` is a class, ``globals`` defaults to
+ ``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults
+ to the ``obj`` class namespace.
+ * If ``obj`` is a callable, ``globals`` defaults to ``obj.__globals__``,
+ although if ``obj`` is a wrapped function (using
+ ``functools.update_wrapper()``) it is first unwrapped.
+
+ .. versionadded:: 3.10
+
+
.. _inspect-stack:
The interpreter stack
stack(), trace() - get info about frames on the stack or in a traceback
signature() - get a Signature object for the callable
+
+ get_annotations() - safely compute an object's annotations
"""
# This module is in the public domain. No warranties.
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20
+
+def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
+ """Compute the annotations dict for an object.
+
+ obj may be a callable, class, or module.
+ Passing in an object of any other type raises TypeError.
+
+ Returns a dict. get_annotations() returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This function handles several details for you:
+
+ * If eval_str is true, values of type str will
+ be un-stringized using eval(). This is intended
+ for use with stringized annotations
+ ("from __future__ import annotations").
+ * If obj doesn't have an annotations dict, returns an
+ empty dict. (Functions and methods always have an
+ annotations dict; classes, modules, and other types of
+ callables may not.)
+ * Ignores inherited annotations on classes. If a class
+ doesn't have its own annotations dict, returns an empty dict.
+ * All accesses to object members and dict values are done
+ using getattr() and dict.get() for safety.
+ * Always, always, always returns a freshly-created dict.
+
+ eval_str controls whether or not values of type str are replaced
+ with the result of calling eval() on those values:
+
+ * If eval_str is true, eval() is called on values of type str.
+ * If eval_str is false (the default), values of type str are unchanged.
+
+ globals and locals are passed in to eval(); see the documentation
+ for eval() for more information. If either globals or locals is
+ None, this function may replace that value with a context-specific
+ default, contingent on type(obj):
+
+ * If obj is a module, globals defaults to obj.__dict__.
+ * If obj is a class, globals defaults to
+ sys.modules[obj.__module__].__dict__ and locals
+ defaults to the obj class namespace.
+ * If obj is a callable, globals defaults to obj.__globals__,
+ although if obj is a wrapped function (using
+ functools.update_wrapper()) it is first unwrapped.
+ """
+ if isinstance(obj, type):
+ # class
+ obj_dict = getattr(obj, '__dict__', None)
+ if obj_dict and hasattr(obj_dict, 'get'):
+ ann = obj_dict.get('__annotations__', None)
+ if isinstance(ann, types.GetSetDescriptorType):
+ ann = None
+ else:
+ ann = None
+
+ obj_globals = None
+ module_name = getattr(obj, '__module__', None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, '__dict__', None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, types.ModuleType):
+ # module
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__dict__')
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__globals__', None)
+ obj_locals = None
+ unwrap = obj
+ else:
+ raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+ if ann is None:
+ return {}
+
+ if not isinstance(ann, dict):
+ raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+ if not ann:
+ return {}
+
+ if not eval_str:
+ return dict(ann)
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, '__wrapped__'):
+ unwrap = unwrap.__wrapped__
+ continue
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
+
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals
+
+ return_value = {key:
+ value if not isinstance(value, str) else eval(value, globals, locals)
+ for key, value in ann.items() }
+ return return_value
+
+
# ----------------------------------------------------------- type-checking
def ismodule(object):
"""Return true if the object is a module.
sig = _signature_from_callable(func,
follow_wrapper_chains=False,
skip_bound_arg=False,
- sigcls=Signature)
+ sigcls=Signature,
+ eval_str=False)
except Exception as ex:
# Most of the times 'signature' will raise ValueError.
# But, it can also raise AttributeError, and, maybe something
isinstance(name, str) and
(defaults is None or isinstance(defaults, tuple)) and
(kwdefaults is None or isinstance(kwdefaults, dict)) and
- isinstance(annotations, dict))
+ (isinstance(annotations, (dict)) or annotations is None) )
def _signature_get_bound_param(spec):
def _signature_from_function(cls, func, skip_bound_arg=True,
- globalns=None, localns=None):
+ globals=None, locals=None, eval_str=False):
"""Private helper: constructs Signature for the given python function."""
is_duck_function = False
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
- annotations = func.__annotations__
+ annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
- globalns=None,
- localns=None,
+ globals=None,
+ locals=None,
+ eval_str=False,
sigcls):
"""Private helper function to get signature for arbitrary
_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)
+ globals=globals,
+ locals=locals,
+ sigcls=sigcls,
+ eval_str=eval_str)
if not callable(obj):
raise TypeError('{!r} is not a callable object'.format(obj))
# of a Python function (Cython functions, for instance), then:
return _signature_from_function(sigcls, obj,
skip_bound_arg=skip_bound_arg,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
if _signature_is_builtin(obj):
return _signature_from_builtin(sigcls, obj,
@classmethod
def from_callable(cls, obj, *,
- follow_wrapped=True, globalns=None, localns=None):
+ follow_wrapped=True, globals=None, locals=None, eval_str=False):
"""Constructs Signature for the given callable object."""
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
@property
def parameters(self):
return rendered
-def signature(obj, *, follow_wrapped=True, globalns=None, localns=None):
+def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False):
"""Get a signature object for the passed callable."""
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
def _main():
from test import inspect_fodder as mod
from test import inspect_fodder2 as mod2
from test import support
+from test import inspect_stock_annotations
+from test import inspect_stringized_annotations
+from test import inspect_stringized_annotations_2
from test.test_import import _ready_to_import
attrs = [a[0] for a in inspect.getmembers(C)]
self.assertNotIn('missing', attrs)
+ def test_get_annotations_with_stock_annotations(self):
+ def foo(a:int, b:str): pass
+ self.assertEqual(inspect.get_annotations(foo), {'a': int, 'b': str})
+
+ foo.__annotations__ = {'a': 'foo', 'b':'str'}
+ self.assertEqual(inspect.get_annotations(foo), {'a': 'foo', 'b': 'str'})
+
+ self.assertEqual(inspect.get_annotations(foo, eval_str=True, locals=locals()), {'a': foo, 'b': str})
+ self.assertEqual(inspect.get_annotations(foo, eval_str=True, globals=locals()), {'a': foo, 'b': str})
+
+ isa = inspect_stock_annotations
+ self.assertEqual(inspect.get_annotations(isa), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(inspect), {}) # inspect module has no annotations
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(inspect, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(inspect, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {})
+
+ def times_three(fn):
+ @functools.wraps(fn)
+ def wrapper(a, b):
+ return fn(a*3, b*3)
+ return wrapper
+
+ wrapped = times_three(isa.function)
+ self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx'))
+ self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
+ self.assertEqual(inspect.get_annotations(wrapped), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass})
+
+ def test_get_annotations_with_stringized_annotations(self):
+ isa = inspect_stringized_annotations
+ self.assertEqual(inspect.get_annotations(isa), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.function), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function2), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function3), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {})
+
+ isa2 = inspect_stringized_annotations_2
+ self.assertEqual(inspect.get_annotations(isa2), {})
+ self.assertEqual(inspect.get_annotations(isa2, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa2, eval_str=False), {})
+
+ def times_three(fn):
+ @functools.wraps(fn)
+ def wrapper(a, b):
+ return fn(a*3, b*3)
+ return wrapper
+
+ wrapped = times_three(isa.function)
+ self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx'))
+ self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
+ self.assertEqual(inspect.get_annotations(wrapped), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+
+ # test that local namespace lookups work
+ self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations), {'x': 'mytype'})
+ self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True), {'x': int})
+
+
class TestIsDataDescriptor(unittest.TestCase):
def test_custom_descriptors(self):
pass
ham = partialmethod(test, c=1)
- self.assertEqual(self.signature(Spam.ham),
+ self.assertEqual(self.signature(Spam.ham, eval_str=False),
((('it', ..., ..., 'positional_or_keyword'),
('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
- self.assertEqual(self.signature(Spam().ham),
+ self.assertEqual(self.signature(Spam().ham, eval_str=False),
((('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
g = partialmethod(test, 1)
- self.assertEqual(self.signature(Spam.g),
+ self.assertEqual(self.signature(Spam.g, eval_str=False),
((('self', ..., 'anno', 'positional_or_keyword'),),
...))
self.assertEqual(sig1.return_annotation, int)
self.assertEqual(sig1.parameters['foo'].annotation, Foo)
- sig2 = signature_func(func, localns=locals())
+ sig2 = signature_func(func, locals=locals())
self.assertEqual(sig2.return_annotation, int)
self.assertEqual(sig2.parameters['foo'].annotation, Foo)
- sig3 = signature_func(func2, globalns={'Bar': int}, localns=locals())
+ sig3 = signature_func(func2, globals={'Bar': int}, locals=locals())
self.assertEqual(sig3.return_annotation, int)
self.assertEqual(sig3.parameters['foo'].annotation, Foo)
self.assertEqual(sig3.parameters['bar'].annotation, 'Bar')
+ def test_signature_eval_str(self):
+ isa = inspect_stringized_annotations
+ sig = inspect.Signature
+ par = inspect.Parameter
+ PORK = inspect.Parameter.POSITIONAL_OR_KEYWORD
+ for signature_func in (inspect.signature, inspect.Signature.from_callable):
+ with self.subTest(signature_func = signature_func):
+ self.assertEqual(
+ signature_func(isa.MyClass),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ )))
+ self.assertEqual(
+ signature_func(isa.function),
+ sig(
+ return_annotation='MyClass',
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation='str'),
+ )))
+ self.assertEqual(
+ signature_func(isa.function2),
+ sig(
+ return_annotation='MyClass',
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation="'str'"),
+ par('c', PORK, annotation="MyClass"),
+ )))
+ self.assertEqual(
+ signature_func(isa.function3),
+ sig(
+ parameters=(
+ par('a', PORK, annotation="'int'"),
+ par('b', PORK, annotation="'str'"),
+ par('c', PORK, annotation="'MyClass'"),
+ )))
+
+ self.assertEqual(signature_func(isa.UnannotatedClass), sig())
+ self.assertEqual(signature_func(isa.unannotated_function),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ par('c', PORK),
+ )))
+
+ self.assertEqual(
+ signature_func(isa.MyClass, eval_str=True),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ )))
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True),
+ sig(
+ return_annotation=isa.MyClass,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation=str),
+ )))
+ self.assertEqual(
+ signature_func(isa.function2, eval_str=True),
+ sig(
+ return_annotation=isa.MyClass,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation='str'),
+ par('c', PORK, annotation=isa.MyClass),
+ )))
+ self.assertEqual(
+ signature_func(isa.function3, eval_str=True),
+ sig(
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation='str'),
+ par('c', PORK, annotation='MyClass'),
+ )))
+
+ globalns = {'int': float, 'str': complex}
+ localns = {'str': tuple, 'MyClass': dict}
+ with self.assertRaises(NameError):
+ signature_func(isa.function, eval_str=True, globals=globalns)
+
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True, locals=localns),
+ sig(
+ return_annotation=dict,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation=tuple),
+ )))
+
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True, globals=globalns, locals=localns),
+ sig(
+ return_annotation=dict,
+ parameters=(
+ par('a', PORK, annotation=float),
+ par('b', PORK, annotation=tuple),
+ )))
+
+ def test_signature_none_annotation(self):
+ class funclike:
+ # Has to be callable, and have correct
+ # __code__, __annotations__, __defaults__, __name__,
+ # and __kwdefaults__ attributes
+
+ def __init__(self, func):
+ self.__name__ = func.__name__
+ self.__code__ = func.__code__
+ self.__annotations__ = func.__annotations__
+ self.__defaults__ = func.__defaults__
+ self.__kwdefaults__ = func.__kwdefaults__
+ self.func = func
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ def foo(): pass
+ foo = funclike(foo)
+ foo.__annotations__ = None
+ for signature_func in (inspect.signature, inspect.Signature.from_callable):
+ with self.subTest(signature_func = signature_func):
+ self.assertEqual(signature_func(foo), inspect.Signature())
+ self.assertEqual(inspect.get_annotations(foo), {})
+
class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):