pola-rs/polars · error · TypeError

expected type 'int | str', got {qualified_type_name(item)!r}

Error message

expected type 'int | str', got {qualified_type_name(item)!r} ({item!r})

What it means

Subscripting a struct expression (pl.col('s').struct[item]) dispatches on the item type in Python: str selects a field by name, int by zero-based index, and anything else raises a TypeError showing the qualified type and value. This happens before any Rust call; slices and expressions are not accepted subscripts.

Source

Thrown at py-polars/src/polars/expr/struct.py:75

        >>> df.select(pl.col("s").struct[0])
        shape: (2, 1)
        ┌─────┐
        │ x   │
        │ --- │
        │ i64 │
        ╞═════╡
        │ 1   │
        │ 2   │
        └─────┘
        """
        if isinstance(item, str):
            return self.field(item)
        elif isinstance(item, int):
            return wrap_expr(self._pyexpr.struct_field_by_index(item))
        else:
            msg = f"expected type 'int | str', got {qualified_type_name(item)!r} ({item!r})"
            raise TypeError(msg)

    def field(self, name: str | list[str], *more_names: str) -> Expr:
        """
        Retrieve one or multiple `Struct` field(s) as a new Series.

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

        Parameters
        ----------
        name
            Name of the struct field to retrieve.
        *more_names
            Additional struct field names.

        Examples
        --------
        >>> df = pl.DataFrame(
        ...     {

View on GitHub (pinned to df599052da)

Solutions

  1. Select by name: .struct['field_name']
  2. Or use a plain int index, coercing foreign scalars: .struct[int(idx)]
  3. For multiple fields use .struct.field(['a', 'b'])
  4. Sanitize numpy scalars at the boundary: int(x) once, where the data enters your code

Example fix

# before
idx = int(np.argmax(counts))
pl.col('s').struct[np.argmax(counts)]

# after
pl.col('s').struct[int(idx)]
# or, more robustly, by name:
pl.col('s').struct['field_name']
Defensive patterns

Strategy: type-guard

Validate before calling

item = int(item) if isinstance(item, (int, float)) else item
if not isinstance(item, (str, int)):
    raise TypeError(f'bad struct subscript: {item!r}')
expr = pl.col('s').struct[item]

Type guard

import operator

def is_struct_key(x: object) -> bool:
    return isinstance(x, str) or (isinstance(x, int) and not isinstance(x, bool))

# also coerce numpy integers at the boundary:
def coerce_struct_key(x: object) -> str | int:
    if hasattr(x, 'item') and not isinstance(x, (str, bytes)):
        x = operator.index(x)  # numpy integers support __index__
    return x  # type: ignore[return-value]

Prevention

When it happens

Trigger: .struct[np.int64(0)] or .struct[0.0] (index computed via numpy or float division — np.int64 is not a Python int); .struct[None]; .struct[1:3] (slice); .struct[pl.col('f')].

Common situations: Indices produced by numpy operations (argmax, where) or read from JSON as floats; dynamic field selection code that sometimes yields None; users expecting slice semantics like pandas.

Related errors


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