GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Avoid needlessly accessing `ForwardRef.__forward_code__` in `evaluate_forward_ref()` · Issue #155278 · python/cpython

Avoid needlessly accessing ForwardRef.__forward_code__ in evaluate_forward_ref() #155278

New issue
Copy link
New issue
Copy link
Closed
Closed
Avoid needlessly accessing ForwardRef.__forward_code__ in evaluate_forward_ref()#155278
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

@Viicos
Viicos
opened on Aug 6, 2026
Issue body actions

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:

cpython/Lib/annotationlib.py

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():

cpython/Lib/typing.py

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_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

  • gh-155278: Avoid needlessly accessing ForwardRef.__forward_code__ in evaluate_forward_ref() #155279

Activity

  1. Viicos
    added
    type-featureA feature request or enhancementA feature request or enhancement
    on Aug 6, 2026
  2. aisk
    added
    performancePerformance or resource usagePerformance or resource usage
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Aug 6, 2026
  3. JelleZijlstra
    added a commit that references this issue on Aug 7, 2026

    gh-155278: Avoid needlessly accessing `ForwardRef.__forward_code__` i…

    5ffefdb
  4. mbeijen
    added a commit that references this issue on Aug 14, 2026

    pythongh-155278: Avoid needlessly accessing `ForwardRef.__forward_cod…

    9e904f7
  5. Wulian233 commented on Aug 15, 2026

    @Wulian233
    Wulian233
    on Aug 15, 2026
    Contributor
    More actions

    Is this can be close?

  6. picnixz
    closed this as completedon Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.