pola-rs/polars · error · TypeError

'offset' must be an integer, string, or expression, not {typ

Error message

'offset' must be an integer, string, or expression, not {type(offset).__name__}

What it means

Expr.list.slice validates `offset` before parsing it into an expression: it must be an int, a column name (str), or an Expr — anything that is a collections.abc.Collection (except str), such as a list, tuple, set, or dict, raises TypeError with the offending type name. The rejection exists because a bare collection is almost always a mistake (e.g. passing a list of per-row offsets that was never wrapped as a Series/Expr) and parse_into_expression would otherwise misinterpret it.

Source

Thrown at py-polars/src/polars/expr/list.py:1132

            end of the list.

        Examples
        --------
        >>> df = pl.DataFrame({"a": [[1, 2, 3, 4], [10, 2, 1]]})
        >>> df.with_columns(slice=pl.col("a").list.slice(1, 2))
        shape: (2, 2)
        ┌─────────────┬───────────┐
        │ a           ┆ slice     │
        │ ---         ┆ ---       │
        │ list[i64]   ┆ list[i64] │
        ╞═════════════╪═══════════╡
        │ [1, 2, … 4] ┆ [2, 3]    │
        │ [10, 2, 1]  ┆ [2, 1]    │
        └─────────────┴───────────┘
        """
        if isinstance(offset, Collection) and not isinstance(offset, str):
            msg = f"'offset' must be an integer, string, or expression, not {type(offset).__name__}"
            raise TypeError(msg)
        if (
            length is not None
            and isinstance(length, Collection)
            and not isinstance(length, str)
        ):
            msg = f"'length' must be an integer, string, or expression, not {type(length).__name__}"
            raise TypeError(msg)

        offset_pyexpr = parse_into_expression(offset)
        length_pyexpr = parse_into_expression(length)
        return wrap_expr(self._pyexpr.list_slice(offset_pyexpr, length_pyexpr))

    def head(self, n: int | str | Expr = 5) -> Expr:
        """
        Slice the first `n` values of every sub-list.

        .. engine-support:: in-memory, streaming, distributed

View on GitHub (pinned to df599052da)

Solutions

  1. For a constant offset pass an int: list.slice(1).
  2. For per-row offsets, put them in a column and pass an expression: list.slice(pl.col('offs')) or the column name as str.
  3. In generic wrappers, convert Sequences with pl.Series(offset) inside pl.lit(...) or select-based expressions before forwarding.

Example fix

# before
pl.col('l').list.slice(offset=[1, 2])

# after
pl.col('l').list.slice(offset=1)
# per-row offsets:
df.with_columns(pl.col('l').list.slice(pl.col('offs')))
Defensive patterns

Strategy: type-guard

Validate before calling

from collections.abc import Collection
if isinstance(offset, Collection) and not isinstance(offset, str):
    raise TypeError(f'offset must be int/str/Expr, got {type(offset).__name__}')

Type guard

from collections.abc import Collection

def is_valid_slice_arg(v) -> bool:
    return v is None or isinstance(v, (int, str)) or hasattr(v, '_pyexpr')

Prevention

When it happens

Trigger: pl.col('l').list.slice([1, 2]) or list.slice(offset=(1,)) — passing a Python list/tuple where an int or expression is expected; also np arrays if registered as collections.

Common situations: Wanting per-row offsets: developers have a Python list of offsets (one per row) and pass it directly instead of a column; API wrappers that accept Sequence[int] and forward it unchanged; confusing slice semantics with Python's list slicing that accepts tuples.

Related errors


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