Skip to content

Commit 7f08704

Browse files
committed
gh-137228: Improve get_type_hints()
This fixes the two incorrect behaviors flagged in #137228 and more generally makes get_type_hints() lean more into using the owner concept from annotationlib instead of trying to infer globals and locals namespaces itself.
1 parent 2d12bdf commit 7f08704

2 files changed

Lines changed: 17 additions & 52 deletions

File tree

Lib/test/test_typing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6633,7 +6633,7 @@ def test_get_type_hints_classes(self):
66336633
self.assertEqual(gth(mod_generics_cache.B),
66346634
{'my_inner_a1': mod_generics_cache.B.A,
66356635
'my_inner_a2': mod_generics_cache.B.A,
6636-
'my_outer_a': mod_generics_cache.A})
6636+
'my_outer_a': mod_generics_cache.B.A})
66376637

66386638
def test_get_type_hints_classes_no_implicit_optional(self):
66396639
class WithNoneDefault:
@@ -8762,15 +8762,15 @@ def test_get_type_hints(self):
87628762
def test_get_type_hints_generic(self):
87638763
self.assertEqual(
87648764
get_type_hints(BarGeneric),
8765-
{'a': typing.Optional[T], 'b': int}
8765+
{'a': typing.Optional[_typed_dict_helper.T], 'b': int}
87668766
)
87678767

87688768
class FooBarGeneric(BarGeneric[int]):
87698769
c: str
87708770

87718771
self.assertEqual(
87728772
get_type_hints(FooBarGeneric),
8773-
{'a': typing.Optional[T], 'b': int, 'c': str}
8773+
{'a': typing.Optional[_typed_dict_helper.T], 'b': int, 'c': str}
87748774
)
87758775

87768776
def test_pep695_generic_typeddict(self):

Lib/typing.py

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,27 +2329,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23292329
if format == Format.STRING:
23302330
hints.update(ann)
23312331
continue
2332-
if globalns is None:
2333-
base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {})
2334-
else:
2335-
base_globals = globalns
2336-
base_locals = dict(vars(base)) if localns is None else localns
2337-
if localns is None and globalns is None:
2338-
# This is surprising, but required. Before Python 3.10,
2339-
# get_type_hints only evaluated the globalns of
2340-
# a class. To maintain backwards compatibility, we reverse
2341-
# the globalns and localns order so that eval() looks into
2342-
# *base_globals* first rather than *base_locals*.
2343-
# This only affects ForwardRefs.
2344-
base_globals, base_locals = base_locals, base_globals
2345-
type_params = base.__type_params__
2346-
base_globals, base_locals = _add_type_params_to_scope(
2347-
type_params, base_globals, base_locals, True)
23482332
for name, value in ann.items():
23492333
if isinstance(value, str):
2350-
value = _make_forward_ref(value, is_argument=False, is_class=True)
2351-
value = _eval_type(value, base_globals, base_locals, (),
2352-
format=format, owner=obj)
2334+
value = _make_forward_ref(value, is_argument=False, is_class=True,
2335+
owner=base)
2336+
value = _eval_type(value, globalns, localns, base.__type_params__,
2337+
format=format, owner=base)
23532338
if value is None:
23542339
value = type(None)
23552340
hints[name] = value
@@ -2370,21 +2355,14 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23702355
if format == Format.STRING:
23712356
return hints
23722357

2373-
if globalns is None:
2374-
if isinstance(obj, types.ModuleType):
2375-
globalns = obj.__dict__
2376-
else:
2377-
nsobj = obj
2378-
# Find globalns for the unwrapped object.
2379-
while hasattr(nsobj, '__wrapped__'):
2380-
nsobj = nsobj.__wrapped__
2381-
globalns = getattr(nsobj, '__globals__', {})
2382-
if localns is None:
2383-
localns = globalns
2384-
elif localns is None:
2385-
localns = globalns
2386-
type_params = getattr(obj, "__type_params__", ())
2387-
globalns, localns = _add_type_params_to_scope(type_params, globalns, localns, False)
2358+
if isinstance(obj, types.ModuleType):
2359+
nsobj = obj
2360+
else:
2361+
nsobj = obj
2362+
# Find globalns for the unwrapped object.
2363+
while hasattr(nsobj, '__wrapped__'):
2364+
nsobj = nsobj.__wrapped__
2365+
type_params = getattr(nsobj, "__type_params__", ())
23882366
for name, value in hints.items():
23892367
if isinstance(value, str):
23902368
# class-level forward refs were handled above, this must be either
@@ -2393,28 +2371,15 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23932371
value,
23942372
is_argument=not isinstance(obj, types.ModuleType),
23952373
is_class=False,
2374+
owner=nsobj,
23962375
)
2397-
value = _eval_type(value, globalns, localns, (), format=format, owner=obj)
2376+
value = _eval_type(value, globalns, localns, type_params, format=format, owner=nsobj)
23982377
if value is None:
23992378
value = type(None)
24002379
hints[name] = value
24012380
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
24022381

24032382

2404-
# Add type parameters to the globals and locals scope. This is needed for
2405-
# compatibility.
2406-
def _add_type_params_to_scope(type_params, globalns, localns, is_class):
2407-
if not type_params:
2408-
return globalns, localns
2409-
globalns = dict(globalns)
2410-
localns = dict(localns)
2411-
for param in type_params:
2412-
if not is_class or param.__name__ not in globalns:
2413-
globalns[param.__name__] = param
2414-
localns.pop(param.__name__, None)
2415-
return globalns, localns
2416-
2417-
24182383
def _strip_annotations(t):
24192384
"""Strip the annotations from a given type."""
24202385
if isinstance(t, _AnnotatedAlias):

0 commit comments

Comments
 (0)