pandas-dev/pandas · error · ValueError

Invalid unary operator {op!r}, valid operators are {UNARY_OP

Error message

Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}

What it means

Raised by UnaryOp.__init__ in pandas.core.computation.ops when the operator token is not in _unary_ops_dict, whose keys are UNARY_OPS_SYMS = ('+','-','~','not'). It is a ValueError chained from the underlying KeyError. Because the AST tokenizer only ever produces these four unary forms, this is effectively an internal invariant guard rather than something reachable from a normal pd.eval/df.query string.

Source

Thrown at pandas/core/computation/ops.py:519

    op : str
        The token used to represent the operator.
    operand : Term or Op
        The Term or Op operand to the operator.

    Raises
    ------
    ValueError
        * If no function associated with the passed operator token is found.
    """

    def __init__(self, op: Literal["+", "-", "~", "not"], operand) -> None:
        super().__init__(op, (operand,))
        self.operand = operand

        try:
            self.func = _unary_ops_dict[op]
        except KeyError as err:
            raise ValueError(
                f"Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}"
            ) from err

    def __call__(self, env) -> MathCall:
        operand = self.operand(env)
        # error: Cannot call function of unknown type
        return self.func(operand)  # type: ignore[operator]

    def __repr__(self) -> str:
        return pprint_thing(f"{self.op}({self.operand})")

    @property
    def return_type(self) -> np.dtype:
        operand = self.operand
        if operand.return_type == np.dtype("bool"):
            return np.dtype("bool")
        if isinstance(operand, Op) and (
            operand.op in _cmp_ops_dict or operand.op in _bool_ops_dict

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Only use '+', '-', '~', or 'not' as unary operators in eval expressions.
  2. Replace '!' with 'not ' (e.g. pd.eval('not (a > 0)')).
  3. If calling UnaryOp directly, validate the token against UNARY_OPS_SYMS before constructing.

Example fix

# before
from pandas.core.computation.ops import UnaryOp, Term
UnaryOp('!', term)  # ValueError

# after (in an expression)
import pandas as pd
pd.eval('not (a > 0)')   # use Python 'not'
# after (bitwise NOT on integers/bools)
pd.eval('~b')            # b must be bool or int
Defensive patterns

Strategy: validation

Validate before calling

from pandas.core.computation.ops import UNARY_OPS_SYMS

def assert_unary_op(op: str) -> str:
    if op not in UNARY_OPS_SYMS:
        raise ValueError(f'{op!r} not a valid unary op; use {UNARY_OPS_SYMS}')
    return op

Type guard

from pandas.core.computation.ops import UNARY_OPS_SYMS

def is_supported_unary_op(op: str) -> bool:
    return op in UNARY_OPS_SYMS

Try / catch

try:
    UnaryOp(op, operand)
except ValueError as e:
    if 'Invalid unary operator' in str(e):
        # remap to a supported token or skip eval
        ...
    raise

Prevention

When it happens

Trigger: Constructing ops.UnaryOp directly with an unsupported token (e.g. UnaryOp('!', term)), or a custom engine emitting an exotic unary token. Public eval expressions cannot reach this path because the parser would have rejected the token at parse time.

Common situations: Third-party libraries or experimental code that builds Op trees by hand; users assuming C/JS-style '!' or 'not()' syntax is honored. Python's `not` IS supported, but '!' is not.

Related errors


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