{"record":{"id":"d9e047b201fa28b2","repo":"pandas-dev/pandas","slug":"invalid-unary-operator-op-r-valid-operators-are","errorCode":null,"errorMessage":"Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}","messagePattern":"Invalid unary operator (.+?), valid operators are (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/ops.py","lineNumber":519,"sourceCode":"    op : str\n        The token used to represent the operator.\n    operand : Term or Op\n        The Term or Op operand to the operator.\n\n    Raises\n    ------\n    ValueError\n        * If no function associated with the passed operator token is found.\n    \"\"\"\n\n    def __init__(self, op: Literal[\"+\", \"-\", \"~\", \"not\"], operand) -> None:\n        super().__init__(op, (operand,))\n        self.operand = operand\n\n        try:\n            self.func = _unary_ops_dict[op]\n        except KeyError as err:\n            raise ValueError(\n                f\"Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}\"\n            ) from err\n\n    def __call__(self, env) -> MathCall:\n        operand = self.operand(env)\n        # error: Cannot call function of unknown type\n        return self.func(operand)  # type: ignore[operator]\n\n    def __repr__(self) -> str:\n        return pprint_thing(f\"{self.op}({self.operand})\")\n\n    @property\n    def return_type(self) -> np.dtype:\n        operand = self.operand\n        if operand.return_type == np.dtype(\"bool\"):\n            return np.dtype(\"bool\")\n        if isinstance(operand, Op) and (\n            operand.op in _cmp_ops_dict or operand.op in _bool_ops_dict","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/computation/ops.py#L501-L537","documentation":"Raised in UnaryOp.__init__ when the operator string is not in _unary_ops_dict, whose keys are '+', '-', '~', 'not' (UNARY_OPS_SYMS). The valid set is printed in the message. Like the binary counterpart, this is a defensive guard: the parser only emits those four unary forms, so hitting it implies an internally constructed UnaryOp or an unsupported token such as unary '*'.","triggerScenarios":"Directly constructing UnaryOp(op, operand) with an operator outside ('+', '-', '~', 'not'). Custom code paths that feed an unrecognized unary token into the eval AST visitor.","commonSituations":"Forks/extensions trying to add new unary operators. A parser regression that produces an unexpected unary node. Misuse of internal API by downstream libraries building Op trees by hand.","solutions":["Restrict unary usage to '+', '-', '~', 'not' inside eval/query expressions.","For unsupported unary forms (e.g. logical not on non-bool), rewrite using bitwise/comparison operators: df.query('~(A > 0)') instead of df.query('not (A > 0)') if 'not' is problematic.","If extending the engine, register the operator in _unary_ops_funcs/_unary_ops_dict before constructing UnaryOp."],"exampleFix":"// before\nfrom pandas.core.computation.ops import UnaryOp\nUnaryOp(\"!\", operand)\n\n// after\nUnaryOp(\"~\", operand)","handlingStrategy":"validation","validationCode":"from pandas.core.computation.ops import _unary_ops_dict\n\ndef is_supported_unary_op(op: str) -> bool:\n    return op in _unary_ops_dict\n\nassert is_supported_unary_op(op_token)","typeGuard":"from typing import Literal\n\nSupportedUnaryOp = Literal['+', '-', '~', 'not']\n\ndef is_supported(op: str) -> bool:\n    return op in {'+', '-', '~', 'not'}","tryCatchPattern":"try:\n    UnaryOp(op, operand)\nexcept ValueError as e:\n    if 'Invalid unary operator' in str(e):\n        UnaryOp('~', operand)  # fallback to invert\n    else:\n        raise","preventionTips":["Only emit '+', '-', '~', 'not' as unary tokens in generated expressions.","Register custom unary operators in _unary_ops_dict before constructing UnaryOp.","Unit-test every operator token against _unary_ops_dict."],"tags":["pandas","eval","unary-operator","expression","defensive"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}