Repository navigation
Avoid needlessly accessing ForwardRef.__forward_code__ in evaluate_forward_ref() #155278
Copy link
Copy link
Closed
Closed
Copy link
Labels
performancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Description
Feature or enhancement
Proposal:
Following #124337, I think we could also avoid evaluating __forward_code__ in _make_forward_ref() (called by evaluate_forward_ref()).
In ForwardRef.evaluate(), __forward_code__ is accessed only if necessary:
Lines 193 to 205 in fe3a123
| if arg.isidentifier() and not keyword.iskeyword(arg): | |
| if arg in locals: | |
| return locals[arg] | |
| elif arg in globals: | |
| return globals[arg] | |
| elif hasattr(builtins, arg): | |
| return getattr(builtins, arg) | |
| elif is_forwardref_format: | |
| return self | |
| else: | |
| raise NameError(_NAME_ERROR_MSG.format(name=arg), name=arg) | |
| else: | |
| code = self.__forward_code__ |
but in _make_forward_ref():
Lines 994 to 1003 in fe3a123
| def _make_forward_ref(code, *, parent_fwdref=None, **kwargs): | |
| if parent_fwdref is not None: | |
| if parent_fwdref.__forward_module__ is not None: | |
| kwargs['module'] = parent_fwdref.__forward_module__ | |
| if parent_fwdref.__owner__ is not None: | |
| kwargs['owner'] = parent_fwdref.__owner__ | |
| forward_ref = annotationlib.ForwardRef(code, **kwargs) | |
| # For compatibility, eagerly compile the forwardref's code. | |
| forward_ref.__forward_code__ | |
| return forward_ref |
We can apply the following:
diff --git a/Lib/typing.py b/Lib/typing.py
index 809c0ff8860..29b858ff836 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -25,6 +25,7 @@
import collections.abc
import copyreg
import functools
+import keyword
import operator
import sys
import types
@@ -998,8 +999,13 @@ def _make_forward_ref(code, *, parent_fwdref=None, **kwargs):
if parent_fwdref.__owner__ is not None:
kwargs['owner'] = parent_fwdref.__owner__
forward_ref = annotationlib.ForwardRef(code, **kwargs)
- # For compatibility, eagerly compile the forwardref's code.
- forward_ref.__forward_code__
+ # For compatibility, eagerly compile the forwardref's code so that any
+ # SyntaxError is raised immediately rather than when the forward
+ # reference is evaluated. Similar to 'ForwardRef.evaluate(), we only compile
+ # it if necessary:
+ if not (code.isidentifier() and not keyword.iskeyword(code)):
+ forward_ref.__forward_code__
return forward_refScript
import sys
import timeit
import typing
class MyClass:
pass
def f(a: "int", b: "str", c: "MyClass", d: "MyClass") -> "MyClass":
pass
def bench(label, n, stmt):
t = min(timeit.repeat(stmt, number=n, repeat=5, globals=globals()))
print(f"{label:55s} {t / n * 1e9:9.1f} ns/call ({n} iters, best of 5)")
if __name__ == "__main__":
bench(
"typing._make_forward_ref('MyClass').evaluate(...)",
200_000,
"typing._make_forward_ref('MyClass').evaluate(globals={'MyClass': MyClass})",
)
bench(
"get_type_hints(f) # only identifier forward refs",
20_000,
"typing.get_type_hints(f)",
)Results
| Benchmark | Before | After | Speedup |
|---|---|---|---|
_make_forward_ref('MyClass').evaluate(...) |
9120.3 ns/call | 1401.8 ns/call | 6.5x (-84.6%) |
get_type_hints(f) (identifier-only forward refs) |
63221.9 ns/call | 22211.0 ns/call | 2.85x (-64.9%) |
before (main):
typing._make_forward_ref('MyClass').evaluate(...) 9120.3 ns/call (200000 iters, best of 5)
get_type_hints(f) # only identifier forward refs 63221.9 ns/call (20000 iters, best of 5)
after (patch):
typing._make_forward_ref('MyClass').evaluate(...) 1401.8 ns/call (200000 iters, best of 5)
get_type_hints(f) # only identifier forward refs 22211.0 ns/call (20000 iters, best of 5)
Has this already been discussed elsewhere?
This is a minor feature, which does not need previous discussion elsewhere
Links to previous discussion of this feature:
No response
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
performancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Activity
gh-155278: Avoid needlessly accessing `ForwardRef.__forward_code__` i…
pythongh-155278: Avoid needlessly accessing `ForwardRef.__forward_cod…
Wulian233 commentedon Aug 15, 2026
Is this can be close?