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:
|
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():
|
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_ref
Script
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
Feature or enhancement
Proposal:
Following #124337, I think we could also avoid evaluating
__forward_code__in_make_forward_ref()(called byevaluate_forward_ref()).In
ForwardRef.evaluate(),__forward_code__is accessed only if necessary:cpython/Lib/annotationlib.py
Lines 193 to 205 in fe3a123
but in
_make_forward_ref():cpython/Lib/typing.py
Lines 994 to 1003 in fe3a123
We can apply the following:
Script
Results
_make_forward_ref('MyClass').evaluate(...)get_type_hints(f)(identifier-only forward refs)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
ForwardRef.__forward_code__inevaluate_forward_ref()#155279