{"record":{"id":"d7b62d8d83b0188b","repo":"pandas-dev/pandas","slug":"left-hand-side-of-an-assignment-must-be-a-single-n","errorCode":null,"errorMessage":"left hand side of an assignment must be a single name","messagePattern":"left hand side of an assignment must be a single name","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/expr.py","lineNumber":626,"sourceCode":"        if step is not None:\n            step = self.visit(step).value\n\n        return slice(lower, upper, step)\n\n    def visit_Assign(self, node, **kwargs):\n        \"\"\"\n        support a single assignment node, like\n\n        c = a + b\n\n        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","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/computation/expr.py#L608-L644","documentation":"visit_Assign requires the single target to be an ast.Name (expr.py:625). Subscript assignment ('a[0] = 1') yields an ast.Subscript target and attribute assignment ('a.b = 1') yields an ast.Attribute target, both rejected. Only plain identifier LHS is supported because the assignment path writes target[assigner] = ret with a string key.","triggerScenarios":"df.eval('a[0] = 1'), df.eval('a.b = 1'), df.eval('a[\"x\"] = 1'), or any LHS that is not a bare identifier.","commonSituations":"Trying to update a single cell or a nested attribute via eval. Porting indexing assignments into the string grammar.","solutions":["Assign to a column name directly: df.eval('new_col = a + b').","Do cell-level or attribute-level assignment in plain Python (df.loc[i, 'a'] = 1).","Compute the value with eval and assign the result outside the string."],"exampleFix":"// before\ndf.eval('a[0] = 1')\n// after\ndf.loc[0, 'a'] = 1","handlingStrategy":"validation","validationCode":"import ast\n\ndef validate_lhs_is_name(expr: str) -> None:\n    for stmt in ast.parse(expr, mode='exec').body:\n        if isinstance(stmt, ast.Assign) and not isinstance(stmt.targets[0], ast.Name):\n            raise SyntaxError(\n                'assignment LHS must be a plain name, not a subscript or attribute'\n            )\n\nvalidate_lhs_is_name(expr)","typeGuard":"import ast\n\ndef lhs_is_plain_name(expr: str) -> bool:\n    return all(\n        not isinstance(s, ast.Assign) or isinstance(s.targets[0], ast.Name)\n        for s in ast.parse(expr, mode='exec').body\n    )","tryCatchPattern":"try:\n    df.eval(expr)\nexcept SyntaxError as e:\n    if 'single name' in str(e):\n        # do the indexed/attr assignment in Python instead\n        df.loc[i, 'a'] = value\n    raise","preventionTips":["Use only plain column names on the LHS of eval assignments.","Perform cell-level or attribute-level writes via the object API.","Compute RHS in eval and assign outside when LHS must be indexed."],"tags":["pandas","eval","assignment","syntax","subscript"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}