pola-rs/polars · error · TypeError
unhashable type: 'Expr' Consider hashing '{self}.meta'.
Error message
unhashable type: 'Expr'
Consider hashing '{self}.meta'. What it means
Expr deliberately defines __hash__ to raise TypeError: expressions are lazy AST wrappers without stable hashable identity. The message points at .meta (the MetaExpr introspection view) as the hashable/comparable handle. Dict keys, set members, and `expr in some_dict` membership all hash first, so any of those with an Expr triggers the error. Note `expr in [list]` does not hash and is unaffected.
Source
Thrown at py-polars/src/polars/expr/expr.py:319
return self._pyexpr.to_str()
def __repr__(self) -> str_:
if self._pyexpr is not None:
if len(expr_str := self._pyexpr.to_str()) > 30:
expr_str = f"{expr_str[:30]}…"
return f"<{self.__class__.__name__} [{expr_str!r}] at 0x{id(self):X}>"
else:
return "only during sphinx"
def __str__(self) -> str_:
if self._pyexpr is not None:
return self._pyexpr.to_str()
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()
# operatorsView on GitHub (pinned to df599052da)
Solutions
- Key by the string form: str(expr) (the canonical expression string)
- Use expr.meta (MetaExpr) as the message suggests for structural identity/equality
- Redesign cache keys to be plain data (column name + parameters) rather than the Expr object
Example fix
# before
cache[pl.col('a') + 1] = transform # TypeError: unhashable
# after
cache[str(pl.col('a') + 1)] = transform # stable string key Defensive patterns
Strategy: type-guard
Validate before calling
import polars as pl
def expr_key(e: pl.Expr) -> str:
return str(e) # stable, hashable representation
cache: dict[str, object] = {}
cache[expr_key(pl.col('a') + 1)] = transform # never hash the Expr itself Type guard
import polars as pl
from typing import TypeGuard
def is_polars_expr(x) -> TypeGuard[pl.Expr]:
return isinstance(x, pl.Expr)
# guard before dict/set usage
if is_polars_expr(key):
key = str(key) Try / catch
try:
cache[expr] = f
except TypeError as e:
if "unhashable type: 'Expr'" in str(e):
cache[str(expr)] = f
else:
raise Prevention
- Never use Expr as dict keys or set members; key by str(expr) or expr.meta
- Remember `expr in some_dict` hashes (fails) while `expr in some_list` does not
- Avoid lru_cache on functions taking Expr arguments
When it happens
Trigger: d[pl.col('a')] = 1; pl.col('a') in {pl.col('b')}; set([pl.col('a'), pl.col('b')]); functools.lru_cache on a function that takes an Expr; using Expr as a field of a frozen dataclass placed in a set.
Common situations: Memoising column-transform factories keyed by expression; deduplicating expressions in pipeline builders; putting Exprs into dict-based dispatch tables.
Related errors
- the truth value of an Expr is ambiguous You probably got he
- escape_regex function is unsupported for `Expr`, you may wan
- DataTypeGroup items must be dtypes; found {qualified_type_na
- Only call is implemented not {method}
- `format` must be one of {'binary', 'json'}, got {format!r}
AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16).
Data as JSON: /api/errors/e4e2fe636abbc201.
Report an issue: GitHub.