[Python-checkins] bpo-42904: Change search order of typing.get_type_hints eval (#25632)

gvanrossum webhook-mailer at python.org
Mon Apr 26 13:31:35 EDT 2021


https://github.com/python/cpython/commit/1b1f9852bda85c01ef124858f293e9c13e04ffce
commit: 1b1f9852bda85c01ef124858f293e9c13e04ffce
branch: master
author: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
committer: gvanrossum <gvanrossum at gmail.com>
date: 2021-04-26T10:31:21-07:00
summary:

bpo-42904: Change search order of typing.get_type_hints eval (#25632)

While surprising (searching globals before locals in one specific case), this is needed for backwards compatibility.

files:
A Misc/NEWS.d/next/Library/2021-04-26-23-39-47.bpo-42904.ejjsyR.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 99417d7d36499..c28f390df0207 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -3016,10 +3016,10 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
             {'other': MySet[T], 'return': MySet[T]}
         )
 
-    def test_get_type_hints_classes(self):
+    def test_get_type_hints_classes_str_annotations(self):
         class Foo:
             y = str
-            x: y
+            x: 'y'
         # This previously raised an error under PEP 563.
         self.assertEqual(get_type_hints(Foo), {'x': str})
 
diff --git a/Lib/typing.py b/Lib/typing.py
index 9a3a6a7725a93..762a98ab17193 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1604,7 +1604,8 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
     - If no dict arguments are passed, an attempt is made to use the
       globals from obj (or the respective module's globals for classes),
       and these are also used as the locals.  If the object does not appear
-      to have globals, an empty dictionary is used.
+      to have globals, an empty dictionary is used.  For classes, the search
+      order is globals first then locals.
 
     - If one dict argument is passed, it is used for both globals and
       locals.
@@ -1628,6 +1629,14 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
                 base_globals = globalns
             ann = base.__dict__.get('__annotations__', {})
             base_locals = dict(vars(base)) if localns is None else localns
+            if localns is None and globalns is None:
+                # This is surprising, but required.  Before Python 3.10,
+                # get_type_hints only evaluated the globalns of
+                # a class.  To maintain backwards compatibility, we reverse
+                # the globalns and localns order so that eval() looks into
+                # *base_globals* first rather than *base_locals*.
+                # This only affects ForwardRefs.
+                base_globals, base_locals = base_locals, base_globals
             for name, value in ann.items():
                 if value is None:
                     value = type(None)
diff --git a/Misc/NEWS.d/next/Library/2021-04-26-23-39-47.bpo-42904.ejjsyR.rst b/Misc/NEWS.d/next/Library/2021-04-26-23-39-47.bpo-42904.ejjsyR.rst
new file mode 100644
index 0000000000000..632941ac36267
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-04-26-23-39-47.bpo-42904.ejjsyR.rst
@@ -0,0 +1,5 @@
+For backwards compatbility with previous minor versions of Python,
+if :func:`typing.get_type_hints` receives no namespace dictionary arguments,
+:func:`typing.get_type_hints` will search through the global then local
+namespaces during evaluation of stringized type annotations
+(string forward references) inside a class.



More information about the Python-checkins mailing list