Skip to main content

Python NumPy: How to Fix "ValueError: assignment destination is read-only"

When working with NumPy arrays, especially those derived from external sources like image files (e.g., via Pillow/PIL) or memory-mapped files, you might encounter the ValueError: assignment destination is read-only. This error signifies that you are attempting to modify (assign a new value to an element of) a NumPy array that has been marked as "read-only," meaning its underlying data buffer is not writable. This is a protective measure to prevent accidental modification of data that might be shared or immutable.

This guide will clearly explain why this ValueError occurs, how to check an array's writability using its flags attribute, and provide robust solutions, primarily focusing on creating a writable copy of the array using methods like np.array(), array.copy(), or np.copy().

Understanding the Error: Read-Only NumPy Arrays

NumPy arrays have a set of flags that describe their memory layout and properties. One of these flags is WRITEABLE.

  • If WRITEABLE is True, you can modify the elements of the array.
  • If WRITEABLE is False, the array is read-only, and any attempt to change its elements will raise the ValueError: assignment destination is read-only.

Arrays might be read-only for several reasons:

  • They are created from data sources that are themselves read-only (e.g., some memory-mapped files).
  • They are views of other arrays, and the base array is read-only.
  • Libraries creating NumPy arrays (like Pillow when converting an image) might create read-only arrays by default or under certain conditions to protect the original data.
  • An array's WRITEABLE flag might have been explicitly set to False.

Reproducing the Error (Common Scenario: Arrays from Pillow Images)

A frequent case where this error occurs is when converting an image opened with the Pillow (PIL) library to a NumPy array. Depending on how Pillow manages the image data, the resulting array might initially be read-only.

First, ensure you have Pillow and NumPy installed:

pip install Pillow numpy

Let's assume you have an image file named sample_image.png in your directory.

import numpy as np
# from PIL import Image # Pillow import would be here if actually loading an image

# Context: Imagine 'some_array' was obtained from a source that made it read-only.
# For example, conceptually:
# pil_image = Image.open('some_image.png')
# some_array = np.array(pil_image)
# # And assume 'some_array' ended up being read-only.

# To reliably demonstrate the error, let's create an array and make it read-only:
original_array = np.array([10, 20, 30, 40, 50])
print(f"Original array: {original_array}")

# Manually set the WRITEABLE flag to False to simulate a read-only array
original_array.flags.writeable = False
print(f"Is the array writable now? {original_array.flags.writeable}")

try:
# ⛔️ Attempt to modify an element in the read-only array
print("Attempting to modify original_array[0]...")
original_array[0] = 99 # This line will raise the ValueError
print("Array modified (this line won't be reached).") # Should not print
except ValueError as e:
if "assignment destination is read-only" in str(e):
print(f"Error successfully reproduced: {e}")
else:
# This case is unlikely if the flag was set to False correctly
print(f"An unexpected ValueError occurred: {e}")

Output:

Original array: [10 20 30 40 50]
Is the array writable now? False
Attempting to modify original_array[0]...
Error successfully reproduced: assignment destination is read-only

Explanation:

  • We create original_array.
  • We then explicitly set original_array.flags.writeable = False. This simulates a scenario where an array (perhaps from np.array(Image.open(...)) or another source) is inherently read-only.
  • The attempt to change original_array[0] to 99 then triggers the ValueError: assignment destination is read-only because the array's data buffer can not be modified.

Checking Array Writability: The ndarray.flags Attribute

You can inspect the flags attribute of a NumPy array to see its properties, including whether it's writable.

import numpy as np
from PIL import Image # Assuming Pillow is used as a common source
import os

# Create a dummy image
try:
Image.new('RGB', (10,10)).save('temp_img_flags.png')
pil_img_for_flags = Image.open('temp_img_flags.png')
arr_for_flags_check = np.array(pil_img_for_flags)

print("Flags for array derived from Pillow image:")
print(arr_for_flags_check.flags)
# Example Output:
# Flags for array derived from Pillow image:
# C_CONTIGUOUS : True
# F_CONTIGUOUS : False
# OWNDATA : True <-- Usually True when np.array() makes a copy
# WRITEABLE : True <-- Often True for np.array(Image), but can be False in other contexts
# ALIGNED : True
# WRITEBACKIFCOPY : False

# To demonstrate a read-only scenario:
arr_made_readonly = np.arange(5)
arr_made_readonly.flags.writeable = False
print("\nFlags for a manually set read-only array:")
print(arr_made_readonly.flags)
# Output:
# Flags for a manually set read-only array:
# C_CONTIGUOUS : True
# F_CONTIGUOUS : True
# OWNDATA : True
# WRITEABLE : False <-- This is the key for the error
# ALIGNED : True
# WRITEBACKIFCOPY : False

except Exception as e:
print(f"Flag checking example setup error: {e}")
finally:
if os.path.exists('temp_img_flags.png'): os.remove('temp_img_flags.png')

Output:

Flags for array derived from Pillow image:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False


Flags for a manually set read-only array:
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : False
ALIGNED : True
WRITEBACKIFCOPY : False
note

If WRITEABLE is False, you'll get the error upon trying to modify the array.

Solution 1: Creating a Writable Copy with numpy.array() (Often Implicit)

When you convert an object (like a Pillow Image object) to a NumPy array using np.array(some_object), NumPy often creates a new array that owns its data (i.e., OWNDATA is True). In many such cases, this new array will also be writable by default.

import numpy as np
from PIL import Image
import os

# Create dummy image
try:
Image.new('L', (5,5)).save('writable_copy_test.png') # Grayscale image
pil_image_source = Image.open('writable_copy_test.png')

# ✅ np.array() often creates a new, writable array (a copy)
writable_image_array = np.array(pil_image_source)

print("Flags of array created with np.array(pil_image_source):")
print(writable_image_array.flags)
# Typically, OWNDATA and WRITEABLE will be True here.

if writable_image_array.flags.writeable:
writable_image_array[0, 0] = 100 # Modify a pixel
print("Modification successful on array from np.array().")
print(writable_image_array)
else:
print("Array from np.array() was unexpectedly not writable (check Pillow/NumPy versions or source).")

except Exception as e:
print(f"Error in Solution 1 example: {e}")
finally:
if os.path.exists('writable_copy_test.png'): os.remove('writable_copy_test.png')

Output:

Flags of array created with np.array(pil_image_source):
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False

Modification successful on array from np.array().
[[100 0 0 0 0]
[ 0 0 0 0 0]
[ 0 0 0 0 0]
[ 0 0 0 0 0]
[ 0 0 0 0 0]]
note

If the original source (like the Pillow image object) somehow provided data in a read-only buffer that np.array() tried to use directly without copying (less common for this constructor but possible with some inputs), then you might still get a read-only array. This is where explicit copying is safer.

Solution 2: Explicitly Creating a Writable Copy with array.copy()

If you have a NumPy array that is read-only, the most reliable way to get a modifiable version is to create an explicit copy using the array.copy() method.

import numpy as np

# Simulate a read-only array (e.g., from some operation or source)
original_readonly_array = np.arange(10)
original_readonly_array.flags.writeable = False # Make it read-only
print("Is original_readonly_array writable?", original_readonly_array.flags.writeable)

# ✅ Create a writable copy using the .copy() method
writable_copy = original_readonly_array.copy()

print("Is writable_copy writable?", writable_copy.flags.writeable)
print("Does writable_copy own its data?", writable_copy.flags.owndata)

# Now you can modify the copy
writable_copy[0] = 99
writable_copy[5] = 555
print("Modified writable_copy:")
print(writable_copy)
print()

print("Original_readonly_array remains unchanged:")
print(original_readonly_array)

Output:

Is original_readonly_array writable? False
Is writable_copy writable? True
Does writable_copy own its data? True
Modified writable_copy:
[ 99 1 2 3 4 555 6 7 8 9]

Original_readonly_array remains unchanged:
[0 1 2 3 4 5 6 7 8 9]

The writable_copy is a new array with its own data buffer, which is writable.

Solution 3: Explicitly Creating a Writable Copy with numpy.copy()

The top-level numpy.copy(array) function achieves the same as the array.copy() method.

import numpy as np

# original_readonly_array defined as above
original_readonly_array = np.arange(10)
original_readonly_array.flags.writeable = False # Make it read-only
print("Is original_readonly_array writable?", original_readonly_array.flags.writeable)

# ✅ Create a writable copy using np.copy()
writable_np_copy = np.copy(original_readonly_array)

print("Is writable_np_copy writable?", writable_np_copy.flags.writeable)

writable_np_copy[1] = 111
print("Modified writable_np_copy:")
print(writable_np_copy)

Output:

Is original_readonly_array writable? False
Is writable_np_copy writable? True
Modified writable_np_copy:
[ 0 111 2 3 4 5 6 7 8 9]

A Note on ndarray.setflags(write=True) (and its limitations)

In older NumPy versions or specific scenarios, you might have seen array.setflags(write=True) or array.flags.writeable = True used to try and make an array writable. However, this often fails if the array does not own its data (OWNDATA flag is False) or if the underlying data buffer is genuinely read-only (e.g., from a read-only memory map). Attempting to set WRITEABLE=True on such an array usually raises another ValueError: can not set WRITEABLE flag to True of this array.

import numpy as np

base_array = np.arange(10)
view_of_array = base_array[::2] # view_of_array does not own its data
print("view_of_array.flags.owndata:", view_of_array.flags.owndata) # False

try:
# This will likely fail because view_of_array doesn't own its data
view_of_array.flags.writeable = True
except ValueError as e:
print(f"Error trying to set writeable flag on a view: {e}")

Output:

view_of_array.flags.owndata: False

Therefore, creating a copy is almost always the more reliable solution.

Distinction: np.asarray() vs. np.array() in Copying Behavior

  • np.array(object, ...): This constructor, by default, tries to create a new array, which often involves copying the data. This is why it can sometimes yield a writable array even if the input (like a Pillow image) might have underlying read-only characteristics.
  • np.asarray(object, ...): This function converts the input to an ndarray but avoids copying the data if the input is already an ndarray of the correct type. If object is a read-only array, np.asarray(object) will likely also return a read-only array (or view).

If you encountered the "read-only" error after using np.asarray() on an object that might yield a read-only array (like np.asarray(Image.open(...))), switching to np.array(Image.open(...)) or, even better, np.array(Image.open(...)).copy() might resolve it by ensuring a new, writable data buffer is created.

Conclusion

The ValueError: assignment destination is read-only in NumPy is a protective error preventing modification of arrays whose data is not meant to be changed. The most robust and generally recommended solution is to create a writable copy of the read-only array before attempting modifications:

  • Use the array.copy() method: writable_arr = read_only_arr.copy().
  • Use the numpy.copy(array) function: writable_arr = np.copy(read_only_arr).
  • When converting from other types like Pillow Images, np.array(pil_image) often creates a writable copy, but np.array(pil_image).copy() is even more explicit if you face issues.

Avoid trying to force the WRITEABLE flag to True directly unless you fully understand the memory ownership (OWNDATA) and the nature of the underlying data buffer, as this can lead to other errors or undefined behavior. Working with a copy ensures you have full control over a modifiable version of the data.