You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
annotationlib.ForwardRef.evaluate() ignores explicit globals/locals when the reference holds a closure cell #160948
When a ForwardRef obtained from annotationlib.get_annotations(..., format=Format.FORWARDREF) refers to a single name from an enclosing
function scope, it stores the value in a closure cell (__cell__). ForwardRef.evaluate() returns that cell value immediately, before
looking at the globals and locals arguments — so explicitly passed
namespaces are silently ignored:
fromannotationlibimportFormat, ForwardRef, get_annotationsdefouter():
definner(a: x):
passfr=get_annotations(inner, format=Format.FORWARDREF)["a"]
x=99returnfrfr=outer()
print(fr.evaluate()) # 99 (correct)print(fr.evaluate(locals={"x": 42})) # 99 ← expected 42print(fr.evaluate(globals={"x": 43})) # 99 ← expected 43# A plain ForwardRef without a cell honours the explicit namespaces:print(ForwardRef("x").evaluate(locals={"x": 42})) # 42
This is inconsistent with every other resolution path in the same
method: for a plain ForwardRef, and for references holding multiple
cells (a dict of cells), explicitly passed locals win over the
captured scope. Only the single-cell path flips the precedence.
The documented contract points the other way as well: ForwardRef.evaluate() documents that "The arguments to this method
can be used to provide bindings for names that would otherwise be
undefined" and that "globals and locals are passed to eval(),
representing the global and local namespaces in which the name is
evaluated" (Doc/library/annotationlib.rst).
Expected behaviour: explicitly passed globals/locals take
precedence over the closure cell, as they do on the other paths; the
cell value is used as a fallback when the name is not provided
explicitly (and when no namespaces are passed at all, behaviour is
unchanged).
I have a fix and regression tests ready and will open a PR.
Environment
CPython main (3.16.0a1+dev, 83fe630), built locally; annotationlib
was added in 3.14, and the single-cell shortcut in ForwardRef.evaluate() is present there in the same form.
The lookup should check the closure before looking at globals, you can see this behaviour if you put your value inside a generic forward reference where the nonlocals are placed between the globals and locals.
Activity
pythongh-160948: Fix annotationlib.ForwardRef.evaluate() ignoring exp…
picnixz commentedon Oct 30, 2026
I think I already saw that issue elsewhere. Can you look at past issues?
DavidCEllis commentedon Oct 30, 2026
I'd still expect 99 here.
The lookup should check the closure before looking at globals, you can see this behaviour if you put your value inside a generic forward reference where the nonlocals are placed between the globals and locals.