Skip to content

Commit 74613a4

Browse files
bpo-43817: Add inspect.get_annotations(). (#25522)
Add inspect.get_annotations, which safely computes the annotations defined on an object. It works around the quirks of accessing the annotations from various types of objects, and makes very few assumptions about the object passed in. inspect.get_annotations can also correctly un-stringize stringized annotations. inspect.signature, inspect.from_callable, and inspect.from_function now call inspect.get_annotations to retrieve annotations. This means inspect.signature and inspect.from_callable can now un-stringize stringized annotations, too.
1 parent a62e424 commit 74613a4

7 files changed

+513
-31
lines changed

Doc/library/inspect.rst

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ The Signature object represents the call signature of a callable object and its
562562
return annotation. To retrieve a Signature object, use the :func:`signature`
563563
function.
564564

565-
.. function:: signature(callable, *, follow_wrapped=True, globalns=None, localns=None)
565+
.. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False)
566566

567567
Return a :class:`Signature` object for the given ``callable``::
568568

@@ -584,11 +584,20 @@ function.
584584
Accepts a wide range of Python callables, from plain functions and classes to
585585
:func:`functools.partial` objects.
586586

587-
Raises :exc:`ValueError` if no signature can be provided, and
588-
:exc:`TypeError` if that type of object is not supported.
587+
For objects defined in modules using stringized annotations
588+
(``from __future__ import annotations``), :func:`signature` will
589+
attempt to automatically un-stringize the annotations using
590+
:func:`inspect.get_annotations()`. The
591+
``global``, ``locals``, and ``eval_str`` parameters are passed
592+
into :func:`inspect.get_annotations()` when resolving the
593+
annotations; see the documentation for :func:`inspect.get_annotations()`
594+
for instructions on how to use these parameters.
589595

590-
``globalns`` and ``localns`` are passed into
591-
:func:`typing.get_type_hints` when resolving the annotations.
596+
Raises :exc:`ValueError` if no signature can be provided, and
597+
:exc:`TypeError` if that type of object is not supported. Also,
598+
if the annotations are stringized, and ``eval_str`` is not false,
599+
the ``eval()`` call(s) to un-stringize the annotations could
600+
potentially raise any kind of exception.
592601

593602
A slash(/) in the signature of a function denotes that the parameters prior
594603
to it are positional-only. For more info, see
@@ -600,20 +609,14 @@ function.
600609
unwrap decorated callables.)
601610

602611
.. versionadded:: 3.10
603-
``globalns`` and ``localns`` parameters.
612+
``globals``, ``locals``, and ``eval_str`` parameters.
604613

605614
.. note::
606615

607616
Some callables may not be introspectable in certain implementations of
608617
Python. For example, in CPython, some built-in functions defined in
609618
C provide no metadata about their arguments.
610619

611-
.. note::
612-
613-
Will first try to resolve the annotations, but when it fails and
614-
encounters with an error while that operation, the annotations will be
615-
returned unchanged (as strings).
616-
617620

618621
.. class:: Signature(parameters=None, *, return_annotation=Signature.empty)
619622

@@ -1115,6 +1118,55 @@ Classes and functions
11151118
.. versionadded:: 3.4
11161119

11171120

1121+
.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False)
1122+
1123+
Compute the annotations dict for an object.
1124+
1125+
``obj`` may be a callable, class, or module.
1126+
Passing in an object of any other type raises :exc:`TypeError`.
1127+
1128+
Returns a dict. ``get_annotations()`` returns a new dict every time
1129+
it's called; calling it twice on the same object will return two
1130+
different but equivalent dicts.
1131+
1132+
This function handles several details for you:
1133+
1134+
* If ``eval_str`` is true, values of type ``str`` will
1135+
be un-stringized using :func:`eval()`. This is intended
1136+
for use with stringized annotations
1137+
(``from __future__ import annotations``).
1138+
* If ``obj`` doesn't have an annotations dict, returns an
1139+
empty dict. (Functions and methods always have an
1140+
annotations dict; classes, modules, and other types of
1141+
callables may not.)
1142+
* Ignores inherited annotations on classes. If a class
1143+
doesn't have its own annotations dict, returns an empty dict.
1144+
* All accesses to object members and dict values are done
1145+
using ``getattr()`` and ``dict.get()`` for safety.
1146+
* Always, always, always returns a freshly-created dict.
1147+
1148+
``eval_str`` controls whether or not values of type ``str`` are replaced
1149+
with the result of calling :func:`eval()` on those values:
1150+
1151+
* If eval_str is true, :func:`eval()` is called on values of type ``str``.
1152+
* If eval_str is false (the default), values of type ``str`` are unchanged.
1153+
1154+
``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
1155+
for :func:`eval()` for more information. If ``globals`` or ``locals``
1156+
is ``None``, this function may replace that value with a context-specific
1157+
default, contingent on ``type(obj)``:
1158+
1159+
* If ``obj`` is a module, ``globals`` defaults to ``obj.__dict__``.
1160+
* If ``obj`` is a class, ``globals`` defaults to
1161+
``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults
1162+
to the ``obj`` class namespace.
1163+
* If ``obj`` is a callable, ``globals`` defaults to ``obj.__globals__``,
1164+
although if ``obj`` is a wrapped function (using
1165+
``functools.update_wrapper()``) it is first unwrapped.
1166+
1167+
.. versionadded:: 3.10
1168+
1169+
11181170
.. _inspect-stack:
11191171

11201172
The interpreter stack

Lib/inspect.py

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
stack(), trace() - get info about frames on the stack or in a traceback
2525
2626
signature() - get a Signature object for the callable
27+
28+
get_annotations() - safely compute an object's annotations
2729
"""
2830

2931
# This module is in the public domain. No warranties.
@@ -60,6 +62,122 @@
6062
# See Include/object.h
6163
TPFLAGS_IS_ABSTRACT = 1 << 20
6264

65+
66+
def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
67+
"""Compute the annotations dict for an object.
68+
69+
obj may be a callable, class, or module.
70+
Passing in an object of any other type raises TypeError.
71+
72+
Returns a dict. get_annotations() returns a new dict every time
73+
it's called; calling it twice on the same object will return two
74+
different but equivalent dicts.
75+
76+
This function handles several details for you:
77+
78+
* If eval_str is true, values of type str will
79+
be un-stringized using eval(). This is intended
80+
for use with stringized annotations
81+
("from __future__ import annotations").
82+
* If obj doesn't have an annotations dict, returns an
83+
empty dict. (Functions and methods always have an
84+
annotations dict; classes, modules, and other types of
85+
callables may not.)
86+
* Ignores inherited annotations on classes. If a class
87+
doesn't have its own annotations dict, returns an empty dict.
88+
* All accesses to object members and dict values are done
89+
using getattr() and dict.get() for safety.
90+
* Always, always, always returns a freshly-created dict.
91+
92+
eval_str controls whether or not values of type str are replaced
93+
with the result of calling eval() on those values:
94+
95+
* If eval_str is true, eval() is called on values of type str.
96+
* If eval_str is false (the default), values of type str are unchanged.
97+
98+
globals and locals are passed in to eval(); see the documentation
99+
for eval() for more information. If either globals or locals is
100+
None, this function may replace that value with a context-specific
101+
default, contingent on type(obj):
102+
103+
* If obj is a module, globals defaults to obj.__dict__.
104+
* If obj is a class, globals defaults to
105+
sys.modules[obj.__module__].__dict__ and locals
106+
defaults to the obj class namespace.
107+
* If obj is a callable, globals defaults to obj.__globals__,
108+
although if obj is a wrapped function (using
109+
functools.update_wrapper()) it is first unwrapped.
110+
"""
111+
if isinstance(obj, type):
112+
# class
113+
obj_dict = getattr(obj, '__dict__', None)
114+
if obj_dict and hasattr(obj_dict, 'get'):
115+
ann = obj_dict.get('__annotations__', None)
116+
if isinstance(ann, types.GetSetDescriptorType):
117+
ann = None
118+
else:
119+
ann = None
120+
121+
obj_globals = None
122+
module_name = getattr(obj, '__module__', None)
123+
if module_name:
124+
module = sys.modules.get(module_name, None)
125+
if module:
126+
obj_globals = getattr(module, '__dict__', None)
127+
obj_locals = dict(vars(obj))
128+
unwrap = obj
129+
elif isinstance(obj, types.ModuleType):
130+
# module
131+
ann = getattr(obj, '__annotations__', None)
132+
obj_globals = getattr(obj, '__dict__')
133+
obj_locals = None
134+
unwrap = None
135+
elif callable(obj):
136+
# this includes types.Function, types.BuiltinFunctionType,
137+
# types.BuiltinMethodType, functools.partial, functools.singledispatch,
138+
# "class funclike" from Lib/test/test_inspect... on and on it goes.
139+
ann = getattr(obj, '__annotations__', None)
140+
obj_globals = getattr(obj, '__globals__', None)
141+
obj_locals = None
142+
unwrap = obj
143+
else:
144+
raise TypeError(f"{obj!r} is not a module, class, or callable.")
145+
146+
if ann is None:
147+
return {}
148+
149+
if not isinstance(ann, dict):
150+
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
151+
152+
if not ann:
153+
return {}
154+
155+
if not eval_str:
156+
return dict(ann)
157+
158+
if unwrap is not None:
159+
while True:
160+
if hasattr(unwrap, '__wrapped__'):
161+
unwrap = unwrap.__wrapped__
162+
continue
163+
if isinstance(unwrap, functools.partial):
164+
unwrap = unwrap.func
165+
continue
166+
break
167+
if hasattr(unwrap, "__globals__"):
168+
obj_globals = unwrap.__globals__
169+
170+
if globals is None:
171+
globals = obj_globals
172+
if locals is None:
173+
locals = obj_locals
174+
175+
return_value = {key:
176+
value if not isinstance(value, str) else eval(value, globals, locals)
177+
for key, value in ann.items() }
178+
return return_value
179+
180+
63181
# ----------------------------------------------------------- type-checking
64182
def ismodule(object):
65183
"""Return true if the object is a module.
@@ -1165,7 +1283,8 @@ def getfullargspec(func):
11651283
sig = _signature_from_callable(func,
11661284
follow_wrapper_chains=False,
11671285
skip_bound_arg=False,
1168-
sigcls=Signature)
1286+
sigcls=Signature,
1287+
eval_str=False)
11691288
except Exception as ex:
11701289
# Most of the times 'signature' will raise ValueError.
11711290
# But, it can also raise AttributeError, and, maybe something
@@ -1898,7 +2017,7 @@ def _signature_is_functionlike(obj):
18982017
isinstance(name, str) and
18992018
(defaults is None or isinstance(defaults, tuple)) and
19002019
(kwdefaults is None or isinstance(kwdefaults, dict)) and
1901-
isinstance(annotations, dict))
2020+
(isinstance(annotations, (dict)) or annotations is None) )
19022021

19032022

19042023
def _signature_get_bound_param(spec):
@@ -2151,7 +2270,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
21512270

21522271

21532272
def _signature_from_function(cls, func, skip_bound_arg=True,
2154-
globalns=None, localns=None):
2273+
globals=None, locals=None, eval_str=False):
21552274
"""Private helper: constructs Signature for the given python function."""
21562275

21572276
is_duck_function = False
@@ -2177,7 +2296,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
21772296
positional = arg_names[:pos_count]
21782297
keyword_only_count = func_code.co_kwonlyargcount
21792298
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
2180-
annotations = func.__annotations__
2299+
annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
21812300
defaults = func.__defaults__
21822301
kwdefaults = func.__kwdefaults__
21832302

@@ -2248,8 +2367,9 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
22482367
def _signature_from_callable(obj, *,
22492368
follow_wrapper_chains=True,
22502369
skip_bound_arg=True,
2251-
globalns=None,
2252-
localns=None,
2370+
globals=None,
2371+
locals=None,
2372+
eval_str=False,
22532373
sigcls):
22542374

22552375
"""Private helper function to get signature for arbitrary
@@ -2259,9 +2379,10 @@ def _signature_from_callable(obj, *,
22592379
_get_signature_of = functools.partial(_signature_from_callable,
22602380
follow_wrapper_chains=follow_wrapper_chains,
22612381
skip_bound_arg=skip_bound_arg,
2262-
globalns=globalns,
2263-
localns=localns,
2264-
sigcls=sigcls)
2382+
globals=globals,
2383+
locals=locals,
2384+
sigcls=sigcls,
2385+
eval_str=eval_str)
22652386

22662387
if not callable(obj):
22672388
raise TypeError('{!r} is not a callable object'.format(obj))
@@ -2330,7 +2451,7 @@ def _signature_from_callable(obj, *,
23302451
# of a Python function (Cython functions, for instance), then:
23312452
return _signature_from_function(sigcls, obj,
23322453
skip_bound_arg=skip_bound_arg,
2333-
globalns=globalns, localns=localns)
2454+
globals=globals, locals=locals, eval_str=eval_str)
23342455

23352456
if _signature_is_builtin(obj):
23362457
return _signature_from_builtin(sigcls, obj,
@@ -2854,11 +2975,11 @@ def from_builtin(cls, func):
28542975

28552976
@classmethod
28562977
def from_callable(cls, obj, *,
2857-
follow_wrapped=True, globalns=None, localns=None):
2978+
follow_wrapped=True, globals=None, locals=None, eval_str=False):
28582979
"""Constructs Signature for the given callable object."""
28592980
return _signature_from_callable(obj, sigcls=cls,
28602981
follow_wrapper_chains=follow_wrapped,
2861-
globalns=globalns, localns=localns)
2982+
globals=globals, locals=locals, eval_str=eval_str)
28622983

28632984
@property
28642985
def parameters(self):
@@ -3106,10 +3227,10 @@ def __str__(self):
31063227
return rendered
31073228

31083229

3109-
def signature(obj, *, follow_wrapped=True, globalns=None, localns=None):
3230+
def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False):
31103231
"""Get a signature object for the passed callable."""
31113232
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
3112-
globalns=globalns, localns=localns)
3233+
globals=globals, locals=locals, eval_str=eval_str)
31133234

31143235

31153236
def _main():

Lib/test/inspect_stock_annotations.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
a:int=3
2+
b:str="foo"
3+
4+
class MyClass:
5+
a:int=4
6+
b:str="bar"
7+
def __init__(self, a, b):
8+
self.a = a
9+
self.b = b
10+
def __eq__(self, other):
11+
return isinstance(other, MyClass) and self.a == other.a and self.b == other.b
12+
13+
def function(a:int, b:str) -> MyClass:
14+
return MyClass(a, b)
15+
16+
17+
def function2(a:int, b:"str", c:MyClass) -> MyClass:
18+
pass
19+
20+
21+
def function3(a:"int", b:"str", c:"MyClass"):
22+
pass
23+
24+
25+
class UnannotatedClass:
26+
pass
27+
28+
def unannotated_function(a, b, c): pass

0 commit comments

Comments
 (0)