{"record":{"id":"9510dc65fb1627db","repo":"pandas-dev/pandas","slug":"only-a-single-expression-is-allowed","errorCode":null,"errorMessage":"only a single expression is allowed","messagePattern":"only a single expression is allowed","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/expr.py","lineNumber":424,"sourceCode":"        self.assigner = None\n\n    def visit(self, node, **kwargs):\n        if isinstance(node, str):\n            clean = self.preparser(node)\n            try:\n                node = ast.fix_missing_locations(ast.parse(clean))\n            except SyntaxError as e:\n                if any(iskeyword(x) for x in clean.split()):\n                    e.msg = \"Python keyword not valid identifier in numexpr query\"\n                raise e\n\n        method = f\"visit_{type(node).__name__}\"\n        visitor = getattr(self, method)\n        return visitor(node, **kwargs)\n\n    def visit_Module(self, node, **kwargs):\n        if len(node.body) != 1:\n            raise SyntaxError(\"only a single expression is allowed\")\n        expr = node.body[0]\n        return self.visit(expr, **kwargs)\n\n    def visit_Expr(self, node, **kwargs):\n        return self.visit(node.value, **kwargs)\n\n    def _rewrite_membership_op(self, node, left, right):\n        # the kind of the operator (is actually an instance)\n        op_instance = node.op\n        op_type = type(op_instance)\n\n        # must be two terms and the comparison operator must be ==/!=/in/not in\n        if is_term(left) and is_term(right) and op_type in self.rewrite_map:\n            left_list, right_list = map(_is_list, (left, right))\n            left_str, right_str = map(_is_str, (left, right))\n\n            # if there are any strings or lists in the expression\n            if left_list or right_list or left_str or right_str:","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/computation/expr.py#L406-L442","documentation":"visit_Module (expr.py:422) requires exactly one element in node.body. If Python's parser produces a module with multiple statements (e.g. from semicolon-chained statements parsed in a single Expr call, or a stray extra statement), the visitor refuses. eval.py already splits on newlines, so this fires mainly when a single preparser pass yields multiple top-level statements.","triggerScenarios":"An expression string that parses to more than one top-level statement within a single Expr.visit, e.g. an embedded ';' producing two statements after preprocessing, or a statement followed by an expression.","commonSituations":"Embedding ';'-separated statements assuming eval handles them like Python. Preparser rewrites that accidentally introduce a second statement. Internal recursive eval calls where a sub-expression parses to multiple statements.","solutions":["Split the string into one expression per eval call.","Replace ';' chaining with separate DataFrame.eval invocations.","If building expressions programmatically, validate they parse to a single ast.Expr before passing."],"exampleFix":"// before\npd.eval('a + 1; b + 2')\n// after\n[a + 1, b + 2]  # or two separate pd.eval calls","handlingStrategy":"validation","validationCode":"import ast\n\ndef validate_single_expression(expr: str) -> None:\n    tree = ast.parse(expr, mode='exec')\n    if len(tree.body) != 1:\n        raise SyntaxError(\n            f'expected a single expression, got {len(tree.body)} statements'\n        )\n\nvalidate_single_expression(expr)","typeGuard":"import ast\n\ndef is_single_expression(expr: str) -> bool:\n    return len(ast.parse(expr, mode='exec').body) == 1","tryCatchPattern":"try:\n    pd.eval(expr)\nexcept SyntaxError as e:\n    if 'single expression' in str(e):\n        for stmt in expr.split(';'):\n            pd.eval(stmt.strip())\n    else:\n        raise","preventionTips":["One expression per eval call; avoid ';'-chaining.","When generating strings programmatically, assert they parse to a single statement.","Split compound user input into separate eval invocations."],"tags":["pandas","eval","parser","syntax","ast"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}