pandas-dev/pandas · error · NotImplementedError

'{node_name}' nodes are not implemented

Error message

'{node_name}' nodes are not implemented

What it means

The expression visitor is built with the @disallow decorator (expr.py:346/779) which installs visit_<Node> methods that raise NotImplementedError for an explicit unsupported set (Lambda, Yield, IfExp, DictComp, SetComp, GeneratorExp, Repr, Set, Is, IsNot, plus statement/module/handler nodes). When the AST produced by Python's parse contains any such node, dispatch lands on _node_not_implemented and raises naming the node type. This bounds the eval grammar to what numexpr/pandas can actually compute.

Source

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

    | _unsupported_expr_nodes
) - _hacked_nodes

# we're adding a different assignment in some cases to be equality comparison
# and we don't want `stmt` and friends in their so get only the class whose
# names are capitalized
_base_supported_nodes = (_all_node_names - _unsupported_nodes) | _hacked_nodes
intersection = _unsupported_nodes & _base_supported_nodes
_msg = f"cannot both support and not support {intersection}"
assert not intersection, _msg


def _node_not_implemented(node_name: str) -> Callable[..., None]:
    """
    Return a function that raises a NotImplementedError with a passed node name.
    """

    def f(self, *args, **kwargs):
        raise NotImplementedError(f"'{node_name}' nodes are not implemented")

    return f


# should be bound by BaseExprVisitor but that creates a circular dependency:
# _T is used in disallow, but disallow is used to define BaseExprVisitor
# https://github.com/microsoft/pyright/issues/2315
_T = TypeVar("_T")


def disallow(nodes: set[str]) -> Callable[[type[_T]], type[_T]]:
    """
    Decorator to disallow certain nodes from parsing. Raises a
    NotImplementedError instead.

    Returns
    -------
    callable

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Rewrite the logic using supported operators (e.g. replace ternary with np.where outside eval).
  2. Move the unsupported construct into plain Python and feed its result back as a column.
  3. For None checks, fillna or use a boolean mask instead of 'is' inside query.

Example fix

// before
df.eval('result = a if flag else b')
// after
df['result'] = np.where(df['flag'], df['a'], df['b'])
Defensive patterns

Strategy: validation

Validate before calling

import ast

UNSUPPORTED = {
    'Lambda', 'Yield', 'GeneratorExp', 'IfExp', 'DictComp',
    'SetComp', 'Repr', 'Set', 'Is', 'IsNot',
}

def validate_eval_ast(expr: str) -> None:
    tree = ast.parse(expr, mode='eval')
    present = {type(n).__name__ for n in ast.walk(tree)}
    bad = present & UNSUPPORTED
    if bad:
        raise NotImplementedError(
            f'eval does not support these nodes: {sorted(bad)}'
        )

validate_eval_ast(expr)

Type guard

import ast

def uses_only_supported_nodes(expr: str) -> bool:
    UNSUPPORTED = {
        'Lambda', 'Yield', 'GeneratorExp', 'IfExp',
        'DictComp', 'SetComp', 'Repr', 'Set', 'Is', 'IsNot',
    }
    present = {type(n).__name__ for n in ast.walk(ast.parse(expr, mode='eval'))}
    return not (present & UNSUPPORTED)

Try / catch

try:
    df.eval(expr)
except NotImplementedError as e:
    if 'nodes are not implemented' in str(e):
        # fall back to plain Python computation
        df['result'] = eval(compile(ast.parse(expr, mode='eval'), '<eval>', 'eval'), {}, df.to_dict('series'))
    else:
        raise

Prevention

When it happens

Trigger: df.eval('a if b else c') (IfExp), df.query('a is None') (Is/IsNot), df.eval('lambda x: x') (Lambda), df.eval('(x for x in a)') (GeneratorExp), df.eval('{k: v for ...}') (DictComp).

Common situations: Porting arbitrary Python one-liners into eval strings. Using 'is None' checks in query. Expecting ternary or comprehension support. Upgrading Python versions that emit different AST node names.

Related errors


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