2

When I use the function that doesn't return anything, the input parameters remain globally unchanged.

For example:

def func(var):
    var += 1

a = 0

for i in range(3):
    func(a)
    print(a)

logically results in

0
0
0

By it doesn't seem to work the same when the input is numpy array:

import numpy as np

def func(var):
    var += 1

a = np.zeros(3)  

for i in range(3):
    func(a)
    print(a)

Output:

[1. 1. 1.]
[2. 2. 2.]
[3. 3. 3.]

Thus, all modifications were applied to array globally, not inside the function. Why is it happening? And, more generally, are there any special rules on how to handle np arrays as functions input?

2 Answers2

4

In Python, any value passed to a function is passed by object reference. So, in the first case, where you pass a number to your function, var is set to a reference to the object that reresents the number 1. In Python, even numbers are objects. To icrement var in this case actually means to set it with a reference to the object that represents the number 1+1, which is the object that represents 2. Note that object that represents the number 1 is not changed. Within the function, it is replaced.

When you pass a numpy array to your function it is likewise passed in by object reference. Hence, var now holds a reference to your array a. Incrementing an array by arr += 1 means to add 1 to each of its elements. Hence, the actual content of the object that var references has to change. However, var still references the same object are incrementation.

Take a look at the following code:

import numpy as np

def func(vals):
    print('Before inc: ', id(vals))
    vals += 1
    print('After inc: ', id(vals))

When you pass in a number literal, vals is set to a reference of the object representing the respective number. This object has a unique id, which you can return using the id function. After incrementation, vals is a reference to the object representing a number which is one greater the first one. You can verify that by calling id again after incrementation. So, the output of the above function is something like:

Before inc:  4351114480
After inc:  4351114512

Note that there are two different objects. When now pass in an numpy array like, the resulting ids are the same:

a = np.zeros(3)
func(a)

Before inc:  4496241408
After inc:  4496241408

If you want to modify an array inside of a function and don't want that the apply changes take effect outside of the function, you have to copy your array:

def func(vals):
    _vals = vals.copy()
    # doing stuff with `_vals` won't change the array passed to `vals`
MaxPowers
  • 5,235
  • 2
  • 44
  • 69
0

+= for int (and float, str, ...) creates a new value. Such types are known as immutable, because each individual object cannot be changed.

>>> i = 1
>>> id(i)
4550541072
>>> i += 1
>>> id(i)  # different id
4550541104

This means incrementing such a value inside a function creates a new value inside the function. Any references outside of the function are unaffected.

+= for np.array (and list, Counter, ...) modifies the content. Such types are known as mutable, because each individual object can be changed.

>>> l = [0, 1, 2, 3]
>>> id(l)
4585425088
>>> l += [4]
>>> id(l)
4585425088

This means incrementing such a value inside a function changes the value of the object visible inside and outside of the function. Any references inside and outside of the function point to the exact same object, and show its changed value.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119