How do numpy's in-place operations (e.g. `+=`) work?
The first thing you need to realise is that a += x
doesn't map exactly to a.__iadd__(x)
, instead it maps to a = a.__iadd__(x)
. Notice that the documentation specifically says that in-place operators return their result, and this doesn't have to be self
(although in practice, it usually is). This means a[i] += x
trivially maps to:
a.__setitem__(i, a.__getitem__(i).__iadd__(x))
So, the addition technically happens in-place, but only on a temporary object. There is still potentially one less temporary object created than if it called __add__
, though.
Actually that has nothing to do with numpy. There is no "set/getitem in-place" in python, these things are equivalent to a[indices] = a[indices] + x
. Knowing that, it becomes pretty obvious what is going on. (EDIT: As lvc writes, actually the right hand side is in place, so that it is a[indices] = (a[indices] += x)
if that was legal syntax, that has largly the same effect though)
Of course a += x
actually is in-place, by mapping a to the np.add
out
argument.
It has been discussed before and numpy cannot do anything about it as such. Though there is an idea to have a np.add.at(array, index_expression, x)
to at least allow such operations.
As Ivc explains, there is no in-place item add method, so under the hood it uses __getitem__
, then __iadd__
, then __setitem__
. Here's a way to empirically observe that behavior:
import numpy
class A(numpy.ndarray):
def __getitem__(self, *args, **kwargs):
print "getitem"
return numpy.ndarray.__getitem__(self, *args, **kwargs)
def __setitem__(self, *args, **kwargs):
print "setitem"
return numpy.ndarray.__setitem__(self, *args, **kwargs)
def __iadd__(self, *args, **kwargs):
print "iadd"
return numpy.ndarray.__iadd__(self, *args, **kwargs)
a = A([1,2,3])
print "about to increment a[0]"
a[0] += 1
It prints
about to increment a[0]
getitem
iadd
setitem