pandas-dev/pandas · error · SyntaxError

only a single expression is allowed

Error message

only a single expression is allowed

What it means

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.

Source

Thrown at pandas/core/computation/expr.py:424

        self.assigner = None

    def visit(self, node, **kwargs):
        if isinstance(node, str):
            clean = self.preparser(node)
            try:
                node = ast.fix_missing_locations(ast.parse(clean))
            except SyntaxError as e:
                if any(iskeyword(x) for x in clean.split()):
                    e.msg = "Python keyword not valid identifier in numexpr query"
                raise e

        method = f"visit_{type(node).__name__}"
        visitor = getattr(self, method)
        return visitor(node, **kwargs)

    def visit_Module(self, node, **kwargs):
        if len(node.body) != 1:
            raise SyntaxError("only a single expression is allowed")
        expr = node.body[0]
        return self.visit(expr, **kwargs)

    def visit_Expr(self, node, **kwargs):
        return self.visit(node.value, **kwargs)

    def _rewrite_membership_op(self, node, left, right):
        # the kind of the operator (is actually an instance)
        op_instance = node.op
        op_type = type(op_instance)

        # must be two terms and the comparison operator must be ==/!=/in/not in
        if is_term(left) and is_term(right) and op_type in self.rewrite_map:
            left_list, right_list = map(_is_list, (left, right))
            left_str, right_str = map(_is_str, (left, right))

            # if there are any strings or lists in the expression
            if left_list or right_list or left_str or right_str:

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Split the string into one expression per eval call.
  2. Replace ';' chaining with separate DataFrame.eval invocations.
  3. If building expressions programmatically, validate they parse to a single ast.Expr before passing.

Example fix

// before
pd.eval('a + 1; b + 2')
// after
[a + 1, b + 2]  # or two separate pd.eval calls
Defensive patterns

Strategy: validation

Validate before calling

import ast

def validate_single_expression(expr: str) -> None:
    tree = ast.parse(expr, mode='exec')
    if len(tree.body) != 1:
        raise SyntaxError(
            f'expected a single expression, got {len(tree.body)} statements'
        )

validate_single_expression(expr)

Type guard

import ast

def is_single_expression(expr: str) -> bool:
    return len(ast.parse(expr, mode='exec').body) == 1

Try / catch

try:
    pd.eval(expr)
except SyntaxError as e:
    if 'single expression' in str(e):
        for stmt in expr.split(';'):
            pd.eval(stmt.strip())
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/9510dc65fb1627db. Report an issue: GitHub.