1

Could anyone explain why fun1 doesn't modify the value of the variable y, while fun2 does? I need to modify an array row by row, but updating y at the same time is not the behavior I'm looking for.

def fun1(x):
    x = 2*x
    return x


def fun2(x):
    for i in range(0, x.shape[0]):
        x[i, :] = 2*x[i, :]
    return x


y = np.random.uniform(0, 100, (10, 10))

z1 = fun1(y)
print(np.array(z1 == y).all())
# False

z2 = fun2(y)
print(np.array(z2 == y).all())
# True
Bi Rico
  • 25,283
  • 3
  • 52
  • 75
Cleland
  • 349
  • 1
  • 6
  • 3
    Possible duplicate of [Python functions call by reference](https://stackoverflow.com/questions/13299427/python-functions-call-by-reference) – Miket25 Jul 05 '18 at 15:43
  • 3
    The first reassigns the variable `x`, thus making it local. The second modifies a mutable object. – hpaulj Jul 05 '18 at 15:46
  • does that passing of the variable y into the function not automatically assign it locally as x? @hpaulj – Cleland Jul 05 '18 at 15:54
  • `y` references an `ndarray` object. Initially the local variable `x` references the same object. `2*x` makes a new array. `x[i,:]=` modifies the original array. – hpaulj Jul 05 '18 at 15:59
  • My first comment was a bit off. `x` is a local variable in both cases. When I said 'making it local' I was thinking of a case where `y` is used inside the function (but not passed as an argument). In either case the distinction between reassigning a variable and modifing an object is important. – hpaulj Jul 05 '18 at 16:03

3 Answers3

1

The only way to modify an object and keep the original intact, in general, is to copy the original, I think you're looking for something like:

def fun2(x):
    x = x.copy()
    for i in range(0, x.shape[0]):
        x[i, :] = 2*x[i, :]
    return x

Then answer to your question regarding the apparent difference between fun1 and fun2 is that python, like most OOP languages, is pass-by-object-reference. The first function reassigns the variable declared in the function signature, but doesn't mutate it's input. The second, simply mutates it's input. the To learn more about this, take a look at this article, https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/

Bi Rico
  • 25,283
  • 3
  • 52
  • 75
  • “Hamlet was not written by Shakespeare; it was merely written by a man named Shakespeare.” haha, thanks for the link – Cleland Jul 06 '18 at 09:37
1

Modifying your function to show the id of the objects

def fun1(x):
    print(id(x),id(y))
    x = 2*x
    print(id(x))
    return x

In [315]: y = np.arange(3)
In [316]: id(y)
Out[316]: 140296824014768
In [317]: z = fun1(y)
140296824014768 140296824014768    
140296823720096
In [318]: id(z)
Out[318]: 140296823720096

So the array referenced by y is passed to the function, and can be referenced by both x (the argument variable) and y (the external variable). But the assignment changes the x reference - that object is passed back to z. y is unchanged.

def fun2(x):
    print(id(x), id(y))
    x[0] = 23
    print(id(x))
    return x

With this 2nd function, the assignment changes an element of x, but doesn't change the id of the referenced object. y,x and z all reference the same array.

In [320]: y
Out[320]: array([0, 1, 2])
In [321]: id(y)
Out[321]: 140296824014768
In [322]: z = fun2(y)
140296824014768 140296824014768
140296824014768
In [323]: id(z)
Out[323]: 140296824014768
In [324]: z
Out[324]: array([23,  1,  2])
In [325]: y
Out[325]: array([23,  1,  2])

If we make a copy of y, either before passing it to the function, or inside the function, then modifying x will not modify y.

In [327]: y = np.arange(3)
In [328]: id(y)
Out[328]: 140296823645328
In [329]: z = fun2(y.copy())
140296823647968 140296823645328
140296823647968
In [330]: id(z)
Out[330]: 140296823647968
In [331]: z
Out[331]: array([23,  1,  2])
In [333]: y
Out[333]: array([0, 1, 2])

The fact that we are passing the array to a function doesn't change the need for a copy. We'd get the same behavior even we just performed the action at the top level.

In [334]: y = np.arange(3)
In [335]: x = y.copy()
In [336]: x[:2]=22
In [337]: x
Out[337]: array([22, 22,  2])
In [338]: y
Out[338]: array([0, 1, 2])

We get the same behavior if the object is a list:

In [339]: yl = [1,2,3]
In [340]: fun1(yl)
140296925836360 ...
140296824729096
Out[340]: [1, 2, 3, 1, 2, 3]

In [341]: fun2(yl)
140296925836360 ...
140296925836360
Out[341]: [23, 2, 3]
In [343]: yl
Out[343]: [23, 2, 3]
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanks for this explanation. It's useful to see it with the object ids and that approach should help me understand if I have any similar problems in future. – Cleland Jul 06 '18 at 09:33
0

In fun1, executing x = 2*x reassigns the variable x to a new array. In fun2, the index assignments x[i, :] = __ directly modify the original array.

BoltzmannBrain
  • 5,082
  • 11
  • 46
  • 79