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:
| ifarg.isidentifier() andnotkeyword.iskeyword(arg): |
| ifarginlocals: |
| returnlocals[arg] |
| elifarginglobals: |
| returnglobals[arg] |
| elifhasattr(builtins, arg): |
| returngetattr(builtins, arg) |
| elifis_forwardref_format: |
| returnself |
| else: |
| raiseNameError(_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): |
| ifparent_fwdrefisnotNone: |
| ifparent_fwdref.__forward_module__isnotNone: |
| kwargs['module'] =parent_fwdref.__forward_module__ |
| ifparent_fwdref.__owner__isnotNone: |
| kwargs['owner'] =parent_fwdref.__owner__ |
| forward_ref=annotationlib.ForwardRef(code, **kwargs) |
| # For compatibility, eagerly compile the forwardref's code. |
| forward_ref.__forward_code__ |
| returnforward_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
importsysimporttimeitimporttypingclassMyClass:
passdeff(a: "int", b: "str", c: "MyClass", d: "MyClass") ->"MyClass":
passdefbench(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