{"record":{"id":"02c7712490254a56","repo":"pandas-dev/pandas","slug":"left-hand-side-of-an-assignment-must-be-a-single-r","errorCode":null,"errorMessage":"left hand side of an assignment must be a single resolvable name","messagePattern":"left hand side of an assignment must be a single resolvable name","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/expr.py","lineNumber":637,"sourceCode":"        set the assigner at the top level, must be a Name node which\n        might or might not exist in the resolvers\n\n        \"\"\"\n        if len(node.targets) != 1:\n            raise SyntaxError(\"can only assign a single expression\")\n        if not isinstance(node.targets[0], ast.Name):\n            raise SyntaxError(\"left hand side of an assignment must be a single name\")\n        if self.env.target is None:\n            raise ValueError(\"cannot assign without a target object\")\n\n        try:\n            assigner = self.visit(node.targets[0], **kwargs)\n        except UndefinedVariableError:\n            assigner = node.targets[0].id\n\n        self.assigner = getattr(assigner, \"name\", assigner)\n        if self.assigner is None:\n            raise SyntaxError(\n                \"left hand side of an assignment must be a single resolvable name\"\n            )\n\n        return self.visit(node.value, **kwargs)\n\n    def visit_Attribute(self, node, **kwargs):\n        attr = node.attr\n        value = node.value\n\n        ctx = node.ctx\n        if isinstance(ctx, ast.Load):\n            # resolve the value\n            visited_value = self.visit(value)\n            if hasattr(visited_value, \"value\"):\n                resolved = visited_value.value\n            else:\n                resolved = visited_value(self.env)\n            try:","sourceCodeStart":619,"sourceCodeEnd":655,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/computation/expr.py#L619-L655","documentation":"After visiting the assignment target, expr.py:635 does self.assigner = getattr(assigner, 'name', assigner). If that resolves to None (the visited term has name=None, or the Name visit returned something without a usable name), the assignment LHS is not a resolvable identifier and pandas raises SyntaxError. This is a defensive guard for malformed-but-parsed LHS nodes.","triggerScenarios":"Edge cases where a syntactically-valid LHS visits to a term whose .name attribute is None — e.g. a constant or a Term constructed without a name. Rare in normal use; usually surfaces from dynamically generated expression strings.","commonSituations":"Programmatically building LHS names from data that yields empty/None. Bugs in custom resolvers that return misshapen Term objects. Internal callers constructing AST by hand.","solutions":["Ensure the LHS is a plain, non-empty identifier string.","Validate the generated name is a str and not None before building the expression.","Avoid constructing expressions from untrusted/dynamic LHS tokens."],"exampleFix":"// before\nname = None\ndf.eval(f'{name} = a + b')\n// after\nname = 'c'\ndf.eval(f'{name} = a + b')","handlingStrategy":"validation","validationCode":"import ast\nimport keyword\n\ndef validate_lhs_resolvable(expr: str) -> None:\n    for stmt in ast.parse(expr, mode='exec').body:\n        if isinstance(stmt, ast.Assign):\n            name = stmt.targets[0].id\n            if not name or keyword.iskeyword(name):\n                raise SyntaxError(f'LHS name {name!r} is not a valid resolvable identifier')\n\nvalidate_lhs_resolvable(expr)","typeGuard":"import ast, keyword\n\ndef lhs_is_valid_identifier(expr: str) -> bool:\n    for s in ast.parse(expr, mode='exec').body:\n        if isinstance(s, ast.Assign):\n            n = s.targets[0].id\n            if not n or keyword.iskeyword(n):\n                return False\n    return True","tryCatchPattern":"try:\n    df.eval(expr)\nexcept SyntaxError as e:\n    if 'resolvable name' in str(e):\n        # choose a concrete column name and rebuild\n        df.eval('new_col = a + b')\n    raise","preventionTips":["Always assign to a concrete, non-keyword column name.","When templating LHS from data, validate the name is a non-empty identifier.","Avoid Python keywords as column names in eval assignments."],"tags":["pandas","eval","assignment","validation"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}