ValueError: assignment destination is read-only (NumPy, pandas 3, PIL, tested)

ValueError: assignment destination is read-only means you wrote into a NumPy array whose flags.writeable is False. Common sources: np.frombuffer(bytes), np.broadcast_to, np.asarray(PIL image), and since pandas 3, df[col].to_numpy() and .values. The fix is usually .copy().

1. Read-only arrays from NumPy itself

import numpy as np

a = np.frombuffer(b"\x01\x02\x03", dtype=np.uint8)
print(a.flags.writeable)
try:
    a[0] = 9
except ValueError as e:
    print("ValueError:", e)

b = np.broadcast_to(np.arange(3), (2, 3))
try:
    b[0, 0] = 9
except ValueError as e:
    print("ValueError:", e)

c = np.arange(3)
c.flags.writeable = False
try:
    c += 1
except ValueError as e:
    print("ValueError:", e)

Output:

False
ValueError: assignment destination is read-only
ValueError: assignment destination is read-only
ValueError: output array is read-only

An array is read-only when a.flags.writeable is False. np.frombuffer on immutable bytes, np.broadcast_to (a view that repeats memory), and arrays you lock with flags.writeable = False all refuse writes. In-place operators such as += fail with the related message "output array is read-only".

2. pandas 3: to_numpy() and .values are read-only

import numpy as np
import pandas as pd

df = pd.DataFrame({"x": [1.0, 2.0, 3.0]})
arr = df["x"].to_numpy()
print(pd.__version__, arr.flags.writeable)
try:
    arr[0] = 99
    print("df after:", df["x"].tolist())
except ValueError as e:
    print("ValueError:", e)

vals = df.values
print("df.values writeable:", vals.flags.writeable)

pandas 2.3.3:

2.3.3 True
df after: [99.0, 2.0, 3.0]
df.values writeable: True

pandas 3.1.0:

3.1.0 False
ValueError: assignment destination is read-only
df.values writeable: False

pandas 3 enables Copy-on-Write, so to_numpy() and .values return a read-only view of the column data. On pandas 2 the same write succeeded and silently changed the DataFrame. Code that relied on that now raises. Details on other pandas 3 changes: pandas 3 breaking changes.

import pandas as pd

df = pd.DataFrame({"x": [1.0, 2.0, 3.0]})
arr = df["x"].to_numpy(copy=True)    # your own copy
arr[0] = 99
print(arr, df["x"].tolist())

df.loc[0, "x"] = 99                  # change the DataFrame itself
print(df["x"].tolist())

Output:

[99.  2.  3.] [1.0, 2.0, 3.0]
[99.0, 2.0, 3.0]

Use to_numpy(copy=True) when you want an array to modify on its own (the DataFrame stays unchanged), or write to the DataFrame with .loc when you want the DataFrame to change.

3. Images: np.asarray(PIL image)

import numpy as np
from PIL import Image

img = Image.new("RGB", (2, 2))
px = np.asarray(img)
print(px.flags.writeable)
try:
    px[0, 0] = 255
except ValueError as e:
    print("ValueError:", e)

px2 = np.array(img)          # np.array copies
px2[0, 0] = 255
print(px2[0, 0])

Output:

False
ValueError: assignment destination is read-only
[255 255 255]

np.asarray on a Pillow image returns a read-only array; np.array(img) makes a writable copy (tested with Pillow 13.0.0).

4. Fixes

import numpy as np

a = np.frombuffer(b"\x01\x02\x03", dtype=np.uint8)
try:
    a.setflags(write=True)
except ValueError as e:
    print("ValueError:", e)

w = a.copy()                 # fix 1: copy
w[0] = 9
print(w, w.flags.writeable)

buf = bytearray(b"\x01\x02\x03")
m = np.frombuffer(buf, dtype=np.uint8)   # fix 2: mutable buffer
m[0] = 9
print(m, buf)

Output:

ValueError: cannot set WRITEABLE flag to True of this array
[9 2 3] True
[9 2 3] bytearray(b'\t\x02\x03')

setflags(write=True) only works when the underlying memory is writable; for bytes it raises "cannot set WRITEABLE flag to True of this array". Make a copy with .copy() (or np.array(x)), or build the array from a bytearray, which is mutable: writes then go through to the buffer, as the last line shows.

Related: NumPy RuntimeWarning · pandas SettingWithCopyWarning · numpy has no attribute 'int'.

Frequently asked questions

Why is my NumPy array read-only?

It is a view of memory NumPy may not change: immutable bytes (frombuffer), a broadcast view, a PIL image via np.asarray, or pandas 3 data via to_numpy()/.values. Check a.flags.writeable.

Why did to_numpy() become read-only in pandas 3?

pandas 3 uses Copy-on-Write, so to_numpy() returns a read-only view. Use to_numpy(copy=True) for a writable array, or change the DataFrame with .loc.

Can I just call setflags(write=True)?

Only if the underlying memory is writable. For arrays over bytes it raises 'cannot set WRITEABLE flag to True of this array'; use .copy() instead.

Written with AI assistance. Measured on 2026-10-23 in Docker: python:3-alpine (Python 3.14.7) with numpy 2.5.4, Pillow 13.0.0, and pandas 2.3.3 and 3.1.0. Each block was run verbatim and the output pasted unedited. PlainTables is our extension; Python, NumPy, pandas and Pillow are free.