I don't know what's going on under the hood, but in-place operations on items in NumPy arrays and in Python lists will return the same reference, which IMO can lead to confusing results when passed into a function.
Start with Python
>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True
>>> id(a[2])
12345
>>> id(b[2])
12345
... where 12345
is a unique id
for the location of the value at a[2]
in memory, which is the same as b[2]
.
So a
and b
refer to the same list in memory. Now try in-place addition on an item in the list.
>>> a[2] += 4
>>> a
[1, 2, 7]
>>> b
[1, 2, 7]
>>> a is b
True
>>> id(a[2])
67890
>>> id(b[2])
67890
So in-place addition of the item in the list only changed the value of the item at index 2
, but a
and b
still reference the same list, although the 3rd item in the list was reassigned to a new value, 7
. The reassignment explains why if a = 4
and b = a
were integers (or floats) instead of lists, then a += 1
would cause a
to be reassigned, and then b
and a
would be different references. However, if list addition is called, eg: a += [5]
for a
and b
referencing the same list, it does not reassign a
; they will both be appended.
Now for NumPy
>>> import numpy as np
>>> a = np.array([1, 2, 3], float)
>>> b = a
>>> a is b
True
Again these are the same reference, and in-place operators seem have the same effect as for list in Python:
>>> a += 4
>>> a
array([ 5., 6., 7.])
>>> b
array([ 5., 6., 7.])
In place addition of an ndarray
updates the reference. This is not the same as calling numpy.add
which creates a copy in a new reference.
>>> a = a + 4
>>> a
array([ 9., 10., 11.])
>>> b
array([ 5., 6., 7.])
In-place operations on borrowed references
I think the danger here is if the reference is passed to a different scope.
>>> def f(x):
... x += 4
... return x
The argument reference to x
is passed into the scope of f
which does not make a copy and in fact changes the value at that reference and passes it back.
>>> f(a)
array([ 13., 14., 15.])
>>> f(a)
array([ 17., 18., 19.])
>>> f(a)
array([ 21., 22., 23.])
>>> f(a)
array([ 25., 26., 27.])
The same would be true for a Python list as well:
>>> def f(x, y):
... x += [y]
>>> a = [1, 2, 3]
>>> b = a
>>> f(a, 5)
>>> a
[1, 2, 3, 5]
>>> b
[1, 2, 3, 5]
IMO this can be confusing and sometimes difficult to debug, so I try to only use in-place operators on references that belong to the current scope, and I try be careful of borrowed references.