pandas-dev/pandas · error · UndefinedVariableError
local variable '{key}' is not defined
Error message
local variable '{key}' is not defined What it means
UndefinedVariableError raised by Scope.resolve() in pandas/core/computation/scope.py:245 with is_local=True. It fires specifically when an identifier prefixed with '@' (the query/eval local-variable sigil) cannot be found in the calling frame's scope (self.scope). The '@name' syntax tells pandas to resolve name as a Python local/global in the caller, not as a DataFrame column; if that name does not exist in the caller, you get this 'local variable ... is not defined' variant.
Source
Thrown at pandas/core/computation/scope.py:245
if is_local:
return self.scope[key]
# not a local variable so check in resolvers if we have them
if self.has_resolvers:
return self.resolvers[key]
# if we're here that means that we have no locals and we also have
# no resolvers
assert not is_local and not self.has_resolvers
return self.scope[key]
except KeyError:
try:
# last ditch effort we look in temporaries
# these are created when parsing indexing expressions
# e.g., df[df > 0]
return self.temps[key]
except KeyError as err:
raise UndefinedVariableError(key, is_local) from err
def swapkey(self, old_key: str, new_key: str, new_value=None) -> None:
"""
Replace a variable name, with a potentially new value.
Parameters
----------
old_key : str
Current variable name to replace
new_key : str
New variable name to replace `old_key` with
new_value : object
Value to be replaced along with the possible renaming
"""
if self.has_resolvers:
maps = self.resolvers.maps + self.scope.maps
else:
maps = self.scope.mapsView on GitHub (pinned to 71959b8cb9)
Solutions
- Define the variable in the same scope where query/eval is called before using '@name'.
- If the value is actually a DataFrame column, remove the '@' prefix and reference the column name directly.
- For values that cannot live in the caller's frame (generated strings, exec contexts), pass them explicitly via local_dict on pd.eval instead of '@name'.
- Avoid '@obj.attr' attribute access after the sigil; bind obj.attr to a plain local first, then use '@localname'.
Example fix
// before
df.query("@threshold > b") # local variable 'threshold' is not defined
// after
threshold = 10
df.query("@threshold > b") Defensive patterns
Strategy: validation
Validate before calling
import ast, inspect
def validate_query_locals(expr: str, caller_locals: dict) -> list[str]:
"""Return @-prefixed names in expr that are missing from caller_locals."""
# query/eval strip the '@' before resolving, so collect Name nodes whose
# source span was preceded by '@'. Simple heuristic via regex on tokens.
import re
at_names = re.findall(r'@(\w+)', expr)
return [n for n in at_names if n not in caller_locals]
# usage at the call site
bad = validate_query_locals('@a > b > @c', locals())
assert not bad, f'undefined locals: {bad}' Type guard
def locals_are_defined(expr: str, caller_locals: dict) -> bool:
import re
return all(name in caller_locals for name in re.findall(r'@(\w+)', expr)) Try / catch
import re
from pandas.errors import UndefinedVariableError
try:
result = df.query(expr)
except UndefinedVariableError as e:
msg = str(e)
# NOTE: is_local is encoded only in the message prefix, not as an attribute.
if msg.startswith('local variable '):
m = re.search(r"local variable '([^']+)' is not defined$", msg)
missing = m.group(1) if m else msg
raise NameError(f'missing @local for query: {missing!r}') from e
raise Prevention
- Define every '@name' variable in the exact scope that calls query/eval; avoid generating the string in another module.
- Do not use '@obj.attr'; bind to a plain local first.
- Pass dynamic values via pd.eval(..., local_dict=...) instead of '@name' when the caller frame is unavailable (exec, threads).
- For generated queries, scan for '@\w+' tokens and verify each against the caller's locals()/globals().
When it happens
Trigger: df.query('@a > b') where 'a' is not defined in the calling frame; df.query('@c > 0') inside a function where 'c' was never assigned; referencing '@self.foo' incorrectly (the sigil expects a bare name in scope, not an attribute expression); calling query inside exec()/eval() with no real caller frame for pandas to inspect via sys._getframe.
Common situations: Refactoring code and removing a local variable but leaving its '@name' reference in a query string; using '@' in front of something that is actually meant to be a column (drop the '@'); running query strings generated elsewhere where the intended local is not in scope at the call site; notebook cells run out of order so the referenced local was never defined in the current kernel state.
Related errors
- name '{key}' is not defined
- Variables in expression "{expr}" overlap with builtins: ({s}
- Invalid engine '{engine}' passed, valid engines are {valid_e
- 'numexpr' is not installed or an unsupported version. Cannot
- expr cannot be an empty string
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/f18899cc694dff32.
Report an issue: GitHub.