Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit57fef27

Browse files
gh-133960: Improve typing.evaluate_forward_ref (#133961)
As explained in#133960, this removes most of the behavior differences with ForwardRef.evaluate.The remaining difference is about recursive evaluation of forwardrefs; this is practically usefulin cases where an annotation refers to a type alias that itself is string-valued.This also improves several edge cases that were previously not handled optimally. For example,the function now takes advantage of the partial evaluation behavior of ForwardRef.evaluate() toevaluate more ForwardRefs in the FORWARDREF format.This alsofixes#133959 as a side effect, because the buggy behavior in#133959 derives fromevaluate_forward_ref().
1 parentb51b08a commit57fef27

File tree

4 files changed

+131
-55
lines changed

4 files changed

+131
-55
lines changed

‎Doc/library/typing.rst

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3500,20 +3500,11 @@ Introspection helpers
35003500
Evaluate an:class:`annotationlib.ForwardRef` as a:term:`type hint`.
35013501

35023502
This is similar to calling:meth:`annotationlib.ForwardRef.evaluate`,
3503-
but unlike that method,:func:`!evaluate_forward_ref` also:
3504-
3505-
* Recursively evaluates forward references nested within the type hint.
3506-
* Raises:exc:`TypeError` when it encounters certain objects that are
3507-
not valid type hints.
3508-
* Replaces type hints that evaluate to:const:`!None` with
3509-
:class:`types.NoneType`.
3510-
* Supports the:attr:`~annotationlib.Format.FORWARDREF` and
3511-
:attr:`~annotationlib.Format.STRING` formats.
3503+
but unlike that method,:func:`!evaluate_forward_ref` also
3504+
recursively evaluates forward references nested within the type hint.
35123505

35133506
See the documentation for:meth:`annotationlib.ForwardRef.evaluate` for
3514-
the meaning of the *owner*, *globals*, *locals*, and *type_params* parameters.
3515-
*format* specifies the format of the annotation and is a member of
3516-
the:class:`annotationlib.Format` enum.
3507+
the meaning of the *owner*, *globals*, *locals*, *type_params*, and *format* parameters.
35173508

35183509
..versionadded::3.14
35193510

‎Lib/test/test_typing.py

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6859,12 +6859,10 @@ def test_forward_ref_and_final(self):
68596859
self.assertEqual(hints, {'value':Final})
68606860

68616861
deftest_top_level_class_var(self):
6862-
# https://bugs.python.org/issue45166
6863-
withself.assertRaisesRegex(
6864-
TypeError,
6865-
r'typing.ClassVar\[int\] is not valid as type argument',
6866-
):
6867-
get_type_hints(ann_module6)
6862+
# This is not meaningful but we don't raise for it.
6863+
# https://github.com/python/cpython/issues/133959
6864+
hints=get_type_hints(ann_module6)
6865+
self.assertEqual(hints, {'wrong':ClassVar[int]})
68686866

68696867
deftest_get_type_hints_typeddict(self):
68706868
self.assertEqual(get_type_hints(TotalMovie), {'title':str,'year':int})
@@ -6967,6 +6965,11 @@ def foo(a: 'Callable[..., T]'):
69676965
self.assertEqual(get_type_hints(foo,globals(),locals()),
69686966
{'a':Callable[...,T]})
69696967

6968+
deftest_special_forms_no_forward(self):
6969+
deff(x:ClassVar[int]):
6970+
pass
6971+
self.assertEqual(get_type_hints(f), {'x':ClassVar[int]})
6972+
69706973
deftest_special_forms_forward(self):
69716974

69726975
classC:
@@ -6982,8 +6985,9 @@ class CF:
69826985
self.assertEqual(get_type_hints(C,globals())['b'],Final[int])
69836986
self.assertEqual(get_type_hints(C,globals())['x'],ClassVar)
69846987
self.assertEqual(get_type_hints(C,globals())['y'],Final)
6985-
withself.assertRaises(TypeError):
6986-
get_type_hints(CF,globals()),
6988+
lfi=get_type_hints(CF,globals())['b']
6989+
self.assertIs(get_origin(lfi),list)
6990+
self.assertEqual(get_args(lfi), (Final[int],))
69876991

69886992
deftest_union_forward_recursion(self):
69896993
ValueList=List['Value']
@@ -7216,33 +7220,113 @@ class C(Generic[T]): pass
72167220
classEvaluateForwardRefTests(BaseTestCase):
72177221
deftest_evaluate_forward_ref(self):
72187222
int_ref=ForwardRef('int')
7219-
missing=ForwardRef('missing')
7223+
self.assertIs(typing.evaluate_forward_ref(int_ref),int)
72207224
self.assertIs(
72217225
typing.evaluate_forward_ref(int_ref,type_params=()),
72227226
int,
72237227
)
7228+
self.assertIs(
7229+
typing.evaluate_forward_ref(int_ref,format=annotationlib.Format.VALUE),
7230+
int,
7231+
)
72247232
self.assertIs(
72257233
typing.evaluate_forward_ref(
7226-
int_ref,type_params=(),format=annotationlib.Format.FORWARDREF,
7234+
int_ref,format=annotationlib.Format.FORWARDREF,
72277235
),
72287236
int,
72297237
)
7238+
self.assertEqual(
7239+
typing.evaluate_forward_ref(
7240+
int_ref,format=annotationlib.Format.STRING,
7241+
),
7242+
'int',
7243+
)
7244+
7245+
deftest_evaluate_forward_ref_undefined(self):
7246+
missing=ForwardRef('missing')
7247+
withself.assertRaises(NameError):
7248+
typing.evaluate_forward_ref(missing)
72307249
self.assertIs(
72317250
typing.evaluate_forward_ref(
7232-
missing,type_params=(),format=annotationlib.Format.FORWARDREF,
7251+
missing,format=annotationlib.Format.FORWARDREF,
72337252
),
72347253
missing,
72357254
)
72367255
self.assertEqual(
72377256
typing.evaluate_forward_ref(
7238-
int_ref,type_params=(),format=annotationlib.Format.STRING,
7257+
missing,format=annotationlib.Format.STRING,
72397258
),
7240-
'int',
7259+
"missing",
72417260
)
72427261

7243-
deftest_evaluate_forward_ref_no_type_params(self):
7244-
ref=ForwardRef('int')
7245-
self.assertIs(typing.evaluate_forward_ref(ref),int)
7262+
deftest_evaluate_forward_ref_nested(self):
7263+
ref=ForwardRef("int | list['str']")
7264+
self.assertEqual(
7265+
typing.evaluate_forward_ref(ref),
7266+
int|list[str],
7267+
)
7268+
self.assertEqual(
7269+
typing.evaluate_forward_ref(ref,format=annotationlib.Format.FORWARDREF),
7270+
int|list[str],
7271+
)
7272+
self.assertEqual(
7273+
typing.evaluate_forward_ref(ref,format=annotationlib.Format.STRING),
7274+
"int | list['str']",
7275+
)
7276+
7277+
why=ForwardRef('"\'str\'"')
7278+
self.assertIs(typing.evaluate_forward_ref(why),str)
7279+
7280+
deftest_evaluate_forward_ref_none(self):
7281+
none_ref=ForwardRef('None')
7282+
self.assertIs(typing.evaluate_forward_ref(none_ref),None)
7283+
7284+
deftest_globals(self):
7285+
A="str"
7286+
ref=ForwardRef('list[A]')
7287+
withself.assertRaises(NameError):
7288+
typing.evaluate_forward_ref(ref)
7289+
self.assertEqual(
7290+
typing.evaluate_forward_ref(ref,globals={'A':A}),
7291+
list[str],
7292+
)
7293+
7294+
deftest_owner(self):
7295+
ref=ForwardRef("A")
7296+
7297+
withself.assertRaises(NameError):
7298+
typing.evaluate_forward_ref(ref)
7299+
7300+
# We default to the globals of `owner`,
7301+
# so it no longer raises `NameError`
7302+
self.assertIs(
7303+
typing.evaluate_forward_ref(ref,owner=Loop),A
7304+
)
7305+
7306+
deftest_inherited_owner(self):
7307+
# owner passed to evaluate_forward_ref
7308+
ref=ForwardRef("list['A']")
7309+
self.assertEqual(
7310+
typing.evaluate_forward_ref(ref,owner=Loop),
7311+
list[A],
7312+
)
7313+
7314+
# owner set on the ForwardRef
7315+
ref=ForwardRef("list['A']",owner=Loop)
7316+
self.assertEqual(
7317+
typing.evaluate_forward_ref(ref),
7318+
list[A],
7319+
)
7320+
7321+
deftest_partial_evaluation(self):
7322+
ref=ForwardRef("list[A]")
7323+
withself.assertRaises(NameError):
7324+
typing.evaluate_forward_ref(ref)
7325+
7326+
self.assertEqual(
7327+
typing.evaluate_forward_ref(ref,format=annotationlib.Format.FORWARDREF),
7328+
list[EqualToForwardRef('A')],
7329+
)
72467330

72477331

72487332
classCollectionsAbcTests(BaseTestCase):

‎Lib/typing.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -956,12 +956,8 @@ def evaluate_forward_ref(
956956
"""Evaluate a forward reference as a type hint.
957957
958958
This is similar to calling the ForwardRef.evaluate() method,
959-
but unlike that method, evaluate_forward_ref() also:
960-
961-
* Recursively evaluates forward references nested within the type hint.
962-
* Rejects certain objects that are not valid type hints.
963-
* Replaces type hints that evaluate to None with types.NoneType.
964-
* Supports the *FORWARDREF* and *STRING* formats.
959+
but unlike that method, evaluate_forward_ref() also
960+
recursively evaluates forward references nested within the type hint.
965961
966962
*forward_ref* must be an instance of ForwardRef. *owner*, if given,
967963
should be the object that holds the annotations that the forward reference
@@ -981,23 +977,24 @@ def evaluate_forward_ref(
981977
ifforward_ref.__forward_arg__in_recursive_guard:
982978
returnforward_ref
983979

984-
try:
985-
value=forward_ref.evaluate(globals=globals,locals=locals,
986-
type_params=type_params,owner=owner)
987-
exceptNameError:
988-
ifformat==_lazy_annotationlib.Format.FORWARDREF:
989-
returnforward_ref
990-
else:
991-
raise
992-
993-
type_=_type_check(
994-
value,
995-
"Forward references must evaluate to types.",
996-
is_argument=forward_ref.__forward_is_argument__,
997-
allow_special_forms=forward_ref.__forward_is_class__,
998-
)
980+
ifformatisNone:
981+
format=_lazy_annotationlib.Format.VALUE
982+
value=forward_ref.evaluate(globals=globals,locals=locals,
983+
type_params=type_params,owner=owner,format=format)
984+
985+
if (isinstance(value,_lazy_annotationlib.ForwardRef)
986+
andformat==_lazy_annotationlib.Format.FORWARDREF):
987+
returnvalue
988+
989+
ifisinstance(value,str):
990+
value=_make_forward_ref(value,module=forward_ref.__forward_module__,
991+
owner=ownerorforward_ref.__owner__,
992+
is_argument=forward_ref.__forward_is_argument__,
993+
is_class=forward_ref.__forward_is_class__)
994+
ifownerisNone:
995+
owner=forward_ref.__owner__
999996
return_eval_type(
1000-
type_,
997+
value,
1001998
globals,
1002999
locals,
10031000
type_params,
@@ -2338,12 +2335,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23382335
# This only affects ForwardRefs.
23392336
base_globals,base_locals=base_locals,base_globals
23402337
forname,valueinann.items():
2341-
ifvalueisNone:
2342-
value=type(None)
23432338
ifisinstance(value,str):
23442339
value=_make_forward_ref(value,is_argument=False,is_class=True)
23452340
value=_eval_type(value,base_globals,base_locals,base.__type_params__,
23462341
format=format,owner=obj)
2342+
ifvalueisNone:
2343+
value=type(None)
23472344
hints[name]=value
23482345
ifinclude_extrasorformat==Format.STRING:
23492346
returnhints
@@ -2377,8 +2374,6 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23772374
localns=globalns
23782375
type_params=getattr(obj,"__type_params__", ())
23792376
forname,valueinhints.items():
2380-
ifvalueisNone:
2381-
value=type(None)
23822377
ifisinstance(value,str):
23832378
# class-level forward refs were handled above, this must be either
23842379
# a module-level annotation or a function argument annotation
@@ -2387,7 +2382,10 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23872382
is_argument=notisinstance(obj,types.ModuleType),
23882383
is_class=False,
23892384
)
2390-
hints[name]=_eval_type(value,globalns,localns,type_params,format=format,owner=obj)
2385+
value=_eval_type(value,globalns,localns,type_params,format=format,owner=obj)
2386+
ifvalueisNone:
2387+
value=type(None)
2388+
hints[name]=value
23912389
returnhintsifinclude_extraselse {k:_strip_annotations(t)fork,tinhints.items()}
23922390

23932391

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Simplify and improve:func:`typing.evaluate_forward_ref`. It now no longer
2+
raises errors on certain invalid types. In several situations, it is now
3+
able to evaluate forward references that were previously unsupported.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp