pandas-dev/pandas · error · ValueError

expr must be a string to be evaluated, {type(expr)} given

Error message

expr must be a string to be evaluated, {type(expr)} given

What it means

pd.eval requires an expression string. A DataFrame or Series passed as expr would otherwise be stringified to its (possibly truncated) repr and then parsed, producing a confusing downstream parse error (GH#16289). pandas short-circuits this with an explicit type guard that names the offending type, so the user sees the real problem instead of a misleading SyntaxError from a truncated repr.

Source

Thrown at pandas/core/computation/eval.py:342

    1    pig   20

    We can add a new column using ``pd.eval``:

    >>> pd.eval("double_age = df.age * 2", target=df)
      animal  age  double_age
    0    dog   10          20
    1    pig   20          40
    """
    inplace = validate_bool_kwarg(inplace, "inplace")

    exprs: list[str | BinOp]
    if isinstance(expr, str):
        _check_expression(expr)
        exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""]
    elif isinstance(expr, NDFrame):
        # GH#16289 a Series/DataFrame would otherwise be converted to its
        #  (possibly truncated) repr and parsed, producing a confusing error
        raise ValueError(f"expr must be a string to be evaluated, {type(expr)} given")
    else:
        # ops.BinOp; for internal compat, not intended to be passed by users
        exprs = [expr]
    multi_line = len(exprs) > 1

    if multi_line and target is None:
        raise ValueError(
            "multi-line expressions are only valid in the "
            "context of data, use DataFrame.eval"
        )
    engine = _check_engine(engine)
    _check_parser(parser)
    _check_resolvers(resolvers)

    ret = None
    first_expr = True
    target_modified = False

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Pass a string expression that references the frame's columns, e.g. pd.eval('a + b').
  2. Operate on the frame directly with vectorized ops (df['a'] + df['b']) or df.eval(...).
  3. If you have a stringified repr, build the expression from column names, not from the frame object.

Example fix

// before
pd.eval(df)
// after
pd.eval('a + b', local_dict={'a': df['a'], 'b': df['b']})
Defensive patterns

Strategy: type-guard

Validate before calling

def require_str_expr(expr) -> str:
    if not isinstance(expr, str):
        raise TypeError(
            f'expr must be str, got {type(expr).__name__}; '
            'pass a column expression string instead'
        )
    return expr

expr = require_str_expr(expr)

Type guard

import pandas as pd

def is_eval_expr_string(expr) -> bool:
    return isinstance(expr, str) and not isinstance(expr, (pd.DataFrame, pd.Series))

Try / catch

try:
    pd.eval(expr)
except ValueError as e:
    if 'must be a string' in str(e):
        # operate on the frame directly instead
        result = expr  # or expr.some_vector_op()
    else:
        raise

Prevention

When it happens

Trigger: pd.eval(df), pd.eval(some_series), or any code path that programmatically routes a pandas object into the expr slot of pd.eval instead of a string. Also reachable by feeding eval the result of another computation that returns a frame.

Common situations: Confusing pd.eval with a generic 'evaluate this object' function. Refactoring code where a variable that used to hold a string now holds a DataFrame. Building expression inputs dynamically and forgetting to stringify.

Related errors


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