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.
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index b32b3d3..0ab6530 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -32,6 +32,9 @@
 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
 
@@ -1281,6 +1284,106 @@ class C(metaclass=M):
         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):
@@ -2786,13 +2889,13 @@ def test(it, a, *, c) -> 'spam':
                 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'))
@@ -2803,7 +2906,7 @@ def test(self: 'anno', x):
 
             g = partialmethod(test, 1)
 
-        self.assertEqual(self.signature(Spam.g),
+        self.assertEqual(self.signature(Spam.g, eval_str=False),
                          ((('self', ..., 'anno', 'positional_or_keyword'),),
                           ...))
 
@@ -3265,15 +3368,145 @@ def func2(foo: Foo, bar: 'Bar') -> int: pass
                 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):