Skip to content

Commit 675738a

Browse files
committed
Add a cache for resolved str, test the cache is used, shorten names
1 parent f3b6293 commit 675738a

File tree

2 files changed

+27
-17
lines changed

2 files changed

+27
-17
lines changed

Lib/annotationlib.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Format(enum.IntEnum):
4747
"__cell__",
4848
"__owner__",
4949
"__stringifier_dict__",
50+
"__resolved_str_cache__",
5051
)
5152

5253

@@ -94,6 +95,7 @@ def __init__(
9495
# value later.
9596
self.__code__ = None
9697
self.__ast_node__ = None
98+
self.__resolved_str_cache__ = None
9799

98100
def __init_subclass__(cls, /, *args, **kwds):
99101
raise TypeError("Cannot subclass ForwardRef")
@@ -113,7 +115,7 @@ def evaluate(
113115
"""
114116
match format:
115117
case Format.STRING:
116-
return self.__resolved_forward_str__
118+
return self.__resolved_str__
117119
case Format.VALUE:
118120
is_forwardref_format = False
119121
case Format.FORWARDREF:
@@ -259,24 +261,27 @@ def __forward_arg__(self):
259261
)
260262

261263
@property
262-
def __resolved_forward_str__(self):
264+
def __resolved_str__(self):
263265
# __forward_arg__ with any names from __extra_names__ replaced
264266
# with the type_repr of the value they represent
265-
resolved_str = self.__forward_arg__
266-
names = self.__extra_names__
267-
268-
if names:
269-
# identifiers can be replaced directly
270-
if resolved_str.isidentifier():
271-
if (name_obj := names.get(resolved_str), _sentinel) is not _sentinel:
272-
resolved_str = type_repr(name_obj)
273-
else:
274-
visitor = _ExtraNameFixer(names)
275-
ast_expr = ast.parse(resolved_str, mode="eval").body
276-
node = visitor.visit(ast_expr)
277-
resolved_str = ast.unparse(node)
267+
if self.__resolved_str_cache__ is None:
268+
resolved_str = self.__forward_arg__
269+
names = self.__extra_names__
270+
271+
if names:
272+
# identifiers can be replaced directly
273+
if resolved_str.isidentifier():
274+
if (name_obj := names.get(resolved_str), _sentinel) is not _sentinel:
275+
resolved_str = type_repr(name_obj)
276+
else:
277+
visitor = _ExtraNameFixer(names)
278+
ast_expr = ast.parse(resolved_str, mode="eval").body
279+
node = visitor.visit(ast_expr)
280+
resolved_str = ast.unparse(node)
281+
282+
self.__resolved_str_cache__ = resolved_str
278283

279-
return resolved_str
284+
return self.__resolved_str_cache__
280285

281286
@property
282287
def __forward_code__(self):
@@ -341,7 +346,7 @@ def __repr__(self):
341346
extra.append(", is_class=True")
342347
if self.__owner__ is not None:
343348
extra.append(f", owner={self.__owner__!r}")
344-
return f"ForwardRef({self.__resolved_forward_str__!r}{''.join(extra)})"
349+
return f"ForwardRef({self.__resolved_str__!r}{''.join(extra)})"
345350

346351

347352
_Template = type(t"")
@@ -377,6 +382,7 @@ def __init__(
377382
self.__cell__ = cell
378383
self.__owner__ = owner
379384
self.__stringifier_dict__ = stringifier_dict
385+
self.__resolved_str_cache__ = None # Needed for ForwardRef
380386

381387
def __convert_to_ast(self, other):
382388
if isinstance(other, _Stringifier):

Lib/test/test_annotationlib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,7 +2058,11 @@ def test_evaluate_string_format_extra_names(self):
20582058
def f(a: ref | str): ...
20592059

20602060
fr = get_annotations(f, format=Format.FORWARDREF)['a']
2061+
# Test the cache is not populated before access
2062+
self.assertIsNone(fr.__resolved_str_cache__)
2063+
20612064
self.assertEqual(fr.evaluate(format=Format.STRING), "ref | str")
2065+
self.assertEqual(fr.__resolved_str_cache__, "ref | str")
20622066

20632067
def test_evaluate_forwardref_format(self):
20642068
fr = ForwardRef("undef")

0 commit comments

Comments
 (0)