pola-rs/polars · error

copy not allowed: cast from {arr.dtype} to {dtype} prohibite

Error message

copy not allowed: cast from {arr.dtype} to {dtype} prohibited

What it means

Raised (as RuntimeError, not TypeError) inside Series.__array__ when copy=False was requested but the produced array's dtype differs from the requested dtype, so honoring the request would require a copy. Zero-copy handoff only works when the numpy dtype matches the Series' native representation (e.g. Int64 -> int64, Float64 -> float64, Utf8View -> no match); any cast triggers this.

Source

Thrown at py-polars/src/polars/series/series.py:1614

            dtype = np.dtype("U")

        if copy is None:
            writable, allow_copy = False, True
        elif copy is True:
            writable, allow_copy = True, True
        elif copy is False:
            writable, allow_copy = False, False
        else:
            msg = f"invalid input for `copy`: {copy!r}"
            raise TypeError(msg)

        arr = self.to_numpy(writable=writable, allow_copy=allow_copy)

        if dtype is not None and dtype != arr.dtype:
            if copy is False:
                # TODO: Only raise when data must be copied
                msg = f"copy not allowed: cast from {arr.dtype} to {dtype} prohibited"
                raise RuntimeError(msg)

            arr = arr.__array__(dtype)

        return arr

    def __array_ufunc__(
        self, ufunc: np.ufunc, method: str_, *inputs: Any, **kwargs: Any
    ) -> Series:
        """Numpy universal functions."""
        if self._s.n_chunks() > 1:
            self._s.rechunk(in_place=True)

        s = self._s

        if method == "__call__":
            if ufunc.nout != 1:
                msg = "only ufuncs that return one 1D array are supported"
                raise NotImplementedError(msg)

View on GitHub (pinned to df599052da)

Solutions

  1. Drop copy=False (default allows the copy), or align the requested dtype with the native one: `np.asarray(s, dtype=np.int64, copy=False)` for an Int64 Series.
  2. Make the copy explicit and controlled: `arr = s.to_numpy(); arr32 = arr.astype(np.float32)`.
  3. Cast the Series first so the numpy conversion is already in the target dtype: `s.cast(pl.Float32).to_numpy()`.
  4. Note the TODO in source: the check is coarse - it raises even when a copy might not strictly be needed - so do not rely on copy=False across dtype boundaries.

Example fix

// before
s = pl.Series([1, 2, 3], dtype=pl.Int64)
np.asarray(s, dtype=np.float32, copy=False)  # RuntimeError

// after
s.cast(pl.Float32).to_numpy()
# or accept the copy:
np.asarray(s, dtype=np.float32)
Defensive patterns

Strategy: validation

Validate before calling

NATIVE = {pl.Int64: np.int64, pl.Float64: np.float64, pl.Int32: np.int32, pl.Float32: np.float32}
if np.asarray(s).dtype != target and copy is False:
    s = s.cast(next(k for k, v in NATIVE.items() if v == target))
arr = np.asarray(s, dtype=target, copy=False)

Type guard

def zero_copy_compatible(s: pl.Series, dtype: np.dtype) -> bool:
    return np.asarray(s).dtype == dtype

Try / catch

try:
    arr = np.asarray(s, dtype=target, copy=False)
except RuntimeError as e:
    if 'copy not allowed' not in str(e):
        raise
    arr = np.asarray(s, dtype=target)  # allow the copy

Prevention

When it happens

Trigger: `np.asarray(s, dtype=np.float32, copy=False)` on an Int64 or Float64 Series; `np.asarray(s, dtype=np.int32, copy=False)` on Int64; requesting U/str dtypes on a String Series together with copy=False; the String->'U' auto-dtype branch only applies when dtype is None.

Common situations: NumPy 2 'copy=False means never copy' migrations - old code where copy=False meant 'avoid if possible' now must guarantee exact dtype; performance-tuned pipelines passing copy=False to avoid allocation while still asking for a different precision.

Related errors


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