7

I was reading this question, about immutable numpy arrays, and in a comment to one of the answers someone shows that the given trick does not work when y = x[:] is used rather than y = x.

>>> import numpy as np
>>> x = np.array([1])
>>> y = x
>>> x.flags.writeable = False
>>> y[0] = 5
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    y[0] = 5
ValueError: assignment destination is read-only
>>> del x, y
>>> x = np.array([1])
>>> y = x[:]
>>> x.flags.writeable = False
>>> y[0] = 5
>>> x
array([5])

(Python 3.7.2, numpy 1.16.2)

What even is the difference between these two and why do they behave so differently in this specific case?

EDIT: this does not answer my question because it only asks about the situation using lists, I want to know why the numpy ndarray shows this peculiar behavior where depending on the method of copying modifying the data sometimes does and sometimes doesn't raise an error.

Poseidaan
  • 291
  • 1
  • 9
  • Does this answer your question? [List changes unexpectedly after assignment. How do I clone or copy it to prevent this?](https://stackoverflow.com/questions/2612802/list-changes-unexpectedly-after-assignment-how-do-i-clone-or-copy-it-to-prevent) – iBug Mar 09 '21 at 17:13
  • @Ch3steR I don't think `x[:]` is a copy of `x` (it is basic slicing, should not be) as modifying `y` in the second assignment stills modifies `x`. – Quang Hoang Mar 09 '21 at 17:14
  • @QuangHoang You are right. Retracted my comment. – Ch3steR Mar 09 '21 at 17:16
  • What it is though, is that `x[:]` is a *view* of `x` with the same data but separate meta-data (`.flag.writeable` included). That's why we can write to the memory of `x` via `y`. – Quang Hoang Mar 09 '21 at 17:18

1 Answers1

4

y = x just adds another reference to the existing object, no copying here. This is only adding another name for the same object into the local namespace, so it will behave in every way the same as x.

y = x[:] creates a shallow copy of the numpy array. This is a new Python object, but the underlying array data in memory will be the same. However, the flags are now independent:

>>> x = np.array([1])
>>> y = x[:]
>>> x.flags.owndata, y.flags.owndata
(True, False)

The owndata flag shows that y is just a view into the data of x. Setting the writeable flag on x will not change y's flags, so y still holds a writeable view into the data of x.

>>> x.flags.writeable, y.flags.writeable
(True, True)
>>> x.flags.writeable = False
>>> x.flags.writeable, y.flags.writeable
(False, True)

Note that if you turn off the writeable flag before copying x, then the copy will also have the writeable flag unset, and you will have a read-only shallow copy.

wim
  • 338,267
  • 99
  • 616
  • 750