{"record":{"id":"f18899cc694dff32","repo":"pandas-dev/pandas","slug":"local-variable-key-is-not-defined","errorCode":null,"errorMessage":"local variable '{key}' is not defined","messagePattern":"local variable '(.+?)' is not defined","errorType":"exception","errorClass":"UndefinedVariableError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/scope.py","lineNumber":245,"sourceCode":"            if is_local:\n                return self.scope[key]\n\n            # not a local variable so check in resolvers if we have them\n            if self.has_resolvers:\n                return self.resolvers[key]\n\n            # if we're here that means that we have no locals and we also have\n            # no resolvers\n            assert not is_local and not self.has_resolvers\n            return self.scope[key]\n        except KeyError:\n            try:\n                # last ditch effort we look in temporaries\n                # these are created when parsing indexing expressions\n                # e.g., df[df > 0]\n                return self.temps[key]\n            except KeyError as err:\n                raise UndefinedVariableError(key, is_local) from err\n\n    def swapkey(self, old_key: str, new_key: str, new_value=None) -> None:\n        \"\"\"\n        Replace a variable name, with a potentially new value.\n\n        Parameters\n        ----------\n        old_key : str\n            Current variable name to replace\n        new_key : str\n            New variable name to replace `old_key` with\n        new_value : object\n            Value to be replaced along with the possible renaming\n        \"\"\"\n        if self.has_resolvers:\n            maps = self.resolvers.maps + self.scope.maps\n        else:\n            maps = self.scope.maps","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/computation/scope.py#L227-L263","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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'."],"exampleFix":"// before\ndf.query(\"@threshold > b\")  # local variable 'threshold' is not defined\n\n// after\nthreshold = 10\ndf.query(\"@threshold > b\")","handlingStrategy":"validation","validationCode":"import ast, inspect\n\ndef validate_query_locals(expr: str, caller_locals: dict) -> list[str]:\n    \"\"\"Return @-prefixed names in expr that are missing from caller_locals.\"\"\"\n    # query/eval strip the '@' before resolving, so collect Name nodes whose\n    # source span was preceded by '@'. Simple heuristic via regex on tokens.\n    import re\n    at_names = re.findall(r'@(\\w+)', expr)\n    return [n for n in at_names if n not in caller_locals]\n\n# usage at the call site\nbad = validate_query_locals('@a > b > @c', locals())\nassert not bad, f'undefined locals: {bad}'","typeGuard":"def locals_are_defined(expr: str, caller_locals: dict) -> bool:\n    import re\n    return all(name in caller_locals for name in re.findall(r'@(\\w+)', expr))","tryCatchPattern":"import re\nfrom pandas.errors import UndefinedVariableError\n\ntry:\n    result = df.query(expr)\nexcept UndefinedVariableError as e:\n    msg = str(e)\n    # NOTE: is_local is encoded only in the message prefix, not as an attribute.\n    if msg.startswith('local variable '):\n        m = re.search(r\"local variable '([^']+)' is not defined$\", msg)\n        missing = m.group(1) if m else msg\n        raise NameError(f'missing @local for query: {missing!r}') from e\n    raise","preventionTips":["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()."],"tags":["query","eval","name-resolution","local-variable","undefined-variable"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}