summaryrefslogtreecommitdiff
path: root/Lib/inspect.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py149
1 files changed, 135 insertions, 14 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index b8e247ec25..9f8cc01310 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -24,6 +24,8 @@ Here are some of the useful functions provided by this module:
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.
@@ -60,6 +62,122 @@ for k, v in dis.COMPILER_FLAG_NAMES.items():
# 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.
@@ -1165,7 +1283,8 @@ def getfullargspec(func):
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
@@ -1898,7 +2017,7 @@ def _signature_is_functionlike(obj):
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):
@@ -2151,7 +2270,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
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
@@ -2177,7 +2296,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 = func.__annotations__
+ annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__
@@ -2248,8 +2367,9 @@ 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,
+ globals=None,
+ locals=None,
+ eval_str=False,
sigcls):
"""Private helper function to get signature for arbitrary
@@ -2259,9 +2379,10 @@ def _signature_from_callable(obj, *,
_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))
@@ -2330,7 +2451,7 @@ def _signature_from_callable(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,
@@ -2854,11 +2975,11 @@ class Signature:
@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):
@@ -3106,10 +3227,10 @@ class Signature:
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():