pola-rs/polars · error · TypeError

the truth value of an Expr is ambiguous You probably got he

Error message

the truth value of an Expr is ambiguous

You probably got here by using a Python standard library function instead of the native expressions API.
Here are some things you might want to try:
- instead of `pl.col('a') and pl.col('b')`, use `pl.col('a') & pl.col('b')`
- instead of `pl.col('a') in [y, z]`, use `pl.col('a').is_in([y, z])`
- instead of `max(pl.col('a'), pl.col('b'))`, use `pl.max_horizontal(pl.col('a'), pl.col('b'))`

What it means

Expr defines __bool__ to raise TypeError because a lazy expression has no truth value until it is evaluated against data. Python invokes __bool__ for `if expr:`, `and`, `or`, `not`, builtin any/all, and truthiness checks on max/min results. Polars' message lists the native replacements: &, |, is_in, and horizontal aggregate functions.

Source

Thrown at py-polars/src/polars/expr/expr.py:332

        else:
            return "only during sphinx"

    def __hash__(self) -> int:
        msg = f"unhashable type: 'Expr'\n\nConsider hashing '{self}.meta'."
        raise TypeError(msg)

    def __bool__(self) -> NoReturn:
        msg = (
            "the truth value of an Expr is ambiguous"
            "\n\n"
            "You probably got here by using a Python standard library function instead "
            "of the native expressions API.\n"
            "Here are some things you might want to try:\n"
            "- instead of `pl.col('a') and pl.col('b')`, use `pl.col('a') & pl.col('b')`\n"
            "- instead of `pl.col('a') in [y, z]`, use `pl.col('a').is_in([y, z])`\n"
            "- instead of `max(pl.col('a'), pl.col('b'))`, use `pl.max_horizontal(pl.col('a'), pl.col('b'))`\n"
        )
        raise TypeError(msg)

    def __abs__(self) -> Expr:
        return self.abs()

    # operators
    def __add__(self, other: IntoExpr) -> Expr:
        other_pyexpr = parse_into_expression(other, str_as_lit=True)
        return wrap_expr(self._pyexpr + other_pyexpr)

    def __radd__(self, other: IntoExpr) -> Expr:
        other_pyexpr = parse_into_expression(other, str_as_lit=True)
        return wrap_expr(other_pyexpr + self._pyexpr)

    def __and__(self, other: IntoExprColumn | int | bool) -> Expr:
        other_pyexpr = parse_into_expression(other)
        return wrap_expr(self._pyexpr.and_(other_pyexpr))

    def __rand__(self, other: IntoExprColumn | int | bool) -> Expr:

View on GitHub (pinned to df599052da)

Solutions

  1. Boolean operators: use & | ~ instead of and/or/not
  2. Membership: use .is_in([y, z]) instead of `in [y, z]`
  3. Aggregates: use pl.max_horizontal(...) / pl.min_horizontal(...) instead of builtin max()/min()
  4. When you genuinely need a Python branch, materialise first: value = df.select(expr).item(), then branch on value

Example fix

# before
if pl.col('a') > 5 and pl.col('b') < 3:
    ...  # TypeError at build time
cond = max(pl.col('a'), pl.col('b'))

# after
df.filter((pl.col('a') > 5) & (pl.col('b') < 3))
cond = pl.max_horizontal(pl.col('a'), pl.col('b'))
Defensive patterns

Strategy: type-guard

Validate before calling

import polars as pl

def materialise_bool(e) -> bool:
    if isinstance(e, pl.Expr):
        raise TypeError('cannot branch on an Expr; evaluate it first, e.g. df.select(e).item()')
    return bool(e)

Type guard

import polars as pl
from typing import TypeGuard

def is_polars_expr(x) -> TypeGuard[pl.Expr]:
    return isinstance(x, pl.Expr)

# before any `if x:` on dynamic values
columns = [x for x in parts if not is_polars_expr(x)]

Prevention

When it happens

Trigger: if pl.col('a') > 5: ...; pl.col('a') and pl.col('b'); not pl.col('a').is_null(); max(pl.col('a'), pl.col('b')); assert pl.col('cnt') > 0 — all raise at expression build time.

Common situations: Copy-pasting pandas/pure-Python logic into lazy pipelines; using builtin max/min instead of pl.max_horizontal/pl.min_horizontal; debugging with if/print around expression objects.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/7ec725dc4acfd5dc. Report an issue: GitHub.