This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: pass-by-reference clues
Type: behavior Stage: resolved
Components: Versions:
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: josh.r, skypickle, steven.daprano
Priority: normal Keywords:

Created on 2019-05-21 00:31 by skypickle, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (5)
msg342967 - (view) Author: stefan (skypickle) Date: 2019-05-21 00:31
I often get unexpected results when a called function results in  a change in a variable because the function gets a pass by reference. For example, consider this snippet of code that manipulates the first column of a 3x3 matrix that it gets.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import numpy as np

def changeValue(kernel):
    kernel[0,0]=kernel[0,0]+ 2 
    kernel[1,0]=kernel[1,0]+ 2 
    kernel[2,0]=kernel[2,0]+ 2 
    return kernel

myKernel = np.array((
 [0, -1, 0],
 [-1, 5, -1],
 [0, -1, 0]), dtype="int")
CVkernel=myKernel

print(CVkernel)
a=changeValue(myKernel)
print(a)
print(CVkernel)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I get the following output

[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]

[[ 2 -1  0]
 [ 1  5 -1]
 [ 2 -1  0]]

[[ 2 -1  0]
 [ 1  5 -1]
 [ 2 -1  0]]

The value of myKernel clobbers CVkernel. I think there is an unintentional call-by-reference (pass-by-reference?) going on but I am not sure why.

If I define the function slightly differently

def changeValue2(kernel):
    kernel=kernel + 2 
    return kernel

Then CVkernel is left untouched

[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]

[[2 1 2]
 [1 7 1]
 [2 1 2]]

[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]

What is going on here? 

EDIT Even when I use a 'safe' function call that does not clobber CVkernel, like kernel=kernel + 2 , the id of myKernel and CVkernel are the same.

id of myKernel  139994865303344
myKernel 
[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]
id of CVKernel  139994865303344
CVKernel 
[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]

**call made to changeValue2**

id of myKernel  139994865303344
myKernel 
[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]
id of CVKernel  139994865303344
CVKernel 
[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]
output a 
[[2 1 2]
 [1 7 1]
 [2 1 2]]

Shouldn't the id of each variable be different if they are different instances?

Would it possible for the python interpreter/compiler to let me know when a function is going to clobber a variable that is not used in the function or passed to the function or returned by the function
msg342969 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2019-05-21 01:15
Hi Stefan, and welcome. 

This is not a help desk, you really should ask elsewhere for explanations of how Python works. There are no bugs here: what you are seeing is standard pass-by-object behaviour.

You are misinterpreting what you are seeing. Python is never pass by reference or pass by value.

https://en.wikipedia.org/wiki/Evaluation_strategy

https://www.effbot.org/zone/call-by-object.htm

*All* function objects, whether strings, ints, lists or numpy arrays, are passed as objects. If you want to make a copy, you have to explicitly make a copy. If you don't, and you mutate the object in place, you will see the mutation in both places.


> Shouldn't the id of each variable be different if they are different instances?

Not necessarily: IDs can be reused. Without seeing the actual running code, I can't tell if the IDs have been used or if they are the same ID because they are the same object.

> Would it possible for the python interpreter/compiler to let me know when a function is going to clobber a variable that is not used in the function or passed to the function or returned by the function

Python never clobbers a variable not used in the function. It may however mutate an object which is accessible from both inside and outside a function.
msg342971 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2019-05-21 01:16
1. This is a bug tracker for bugs in the Python language spec and the CPython interpreter, not a general problem solving site.

2. The ids will differ for changeValue2 if you actually call it (kernel = kernel + 2 requires the the old id of kernel differ from the new id, because they both exist simultaneously, and are different objects); I'm guessing you're calling the wrong function or printing the ids of the wrong variables.

3. "Would it possible for the python interpreter/compiler to let me know when a function is going to clobber a variable that is not used in the function or passed to the function or returned by the function" Every bit of clobbering you're seeing involves a variable passed to the function (and sometimes returned by it). CVkernel=myKernel just made two names that bind to the same underlying object, so passing either name as an argument to a function that modifies its arguments will modify what is seen from both names. That's how mutable objects work. This is briefly addressed in the tutorial here: https://docs.python.org/3/tutorial/classes.html#a-word-about-names-and-objects . As a general rule, Python built-ins *either* modify their arguments in place and return nothing (None) *or* return new values leaving the arguments unmodified. It's a matter of programmer discipline to adhere to this practice in your own code (numpy does make it harder, since it uses views extensively, making slicing not an effective way to copy inputs).

All that said, this isn't a bug. It's a longstanding feature of Python alias arguments to avoid expensive deep copies by default; the onus is on the function writer to copy if needed, or to document the behavior if mutation of the arguments is to occur.
msg343050 - (view) Author: stefan (skypickle) Date: 2019-05-21 14:08
Thank you for your reply and the lucid explanation. 
    On Monday, May 20, 2019, 9:15:42 PM EDT, Steven D'Aprano <report@bugs.python.org> wrote:  

Steven D'Aprano <steve+python@pearwood.info> added the comment:

Hi Stefan, and welcome. 

This is not a help desk, you really should ask elsewhere for explanations of how Python works. There are no bugs here: what you are seeing is standard pass-by-object behaviour.

You are misinterpreting what you are seeing. Python is never pass by reference or pass by value.

https://en.wikipedia.org/wiki/Evaluation_strategy

https://www.effbot.org/zone/call-by-object.htm

*All* function objects, whether strings, ints, lists or numpy arrays, are passed as objects. If you want to make a copy, you have to explicitly make a copy. If you don't, and you mutate the object in place, you will see the mutation in both places.

> Shouldn't the id of each variable be different if they are different instances?

Not necessarily: IDs can be reused. Without seeing the actual running code, I can't tell if the IDs have been used or if they are the same ID because they are the same object.

> Would it possible for the python interpreter/compiler to let me know when a function is going to clobber a variable that is not used in the function or passed to the function or returned by the function

Python never clobbers a variable not used in the function. It may however mutate an object which is accessible from both inside and outside a function.

----------
nosy: +steven.daprano
resolution:  -> not a bug
stage:  -> resolved
status: open -> closed

_______________________________________
Python tracker <report@bugs.python.org>
<https://bugs.python.org/issue36980>
_______________________________________
msg343051 - (view) Author: stefan (skypickle) Date: 2019-05-21 14:08
Thank you for your reply and the lucid explanation.

    On Monday, May 20, 2019, 9:17:34 PM EDT, Josh Rosenberg <report@bugs.python.org> wrote:  

Josh Rosenberg <shadowranger+python@gmail.com> added the comment:

1. This is a bug tracker for bugs in the Python language spec and the CPython interpreter, not a general problem solving site.

2. The ids will differ for changeValue2 if you actually call it (kernel = kernel + 2 requires the the old id of kernel differ from the new id, because they both exist simultaneously, and are different objects); I'm guessing you're calling the wrong function or printing the ids of the wrong variables.

3. "Would it possible for the python interpreter/compiler to let me know when a function is going to clobber a variable that is not used in the function or passed to the function or returned by the function" Every bit of clobbering you're seeing involves a variable passed to the function (and sometimes returned by it). CVkernel=myKernel just made two names that bind to the same underlying object, so passing either name as an argument to a function that modifies its arguments will modify what is seen from both names. That's how mutable objects work. This is briefly addressed in the tutorial here: https://docs.python.org/3/tutorial/classes.html#a-word-about-names-and-objects . As a general rule, Python built-ins *either* modify their arguments in place and return nothing (None) *or* return new values leaving the arguments unmodified. It's a matter of programmer discipline to adhere to this practice in your own code (numpy does make it harder, since it uses views extensively, making slicing not an effective way to copy inputs).

All that said, this isn't a bug. It's a longstanding feature of Python alias arguments to avoid expensive deep copies by default; the onus is on the function writer to copy if needed, or to document the behavior if mutation of the arguments is to occur.

----------
nosy: +josh.r

_______________________________________
Python tracker <report@bugs.python.org>
<https://bugs.python.org/issue36980>
_______________________________________
History
Date User Action Args
2022-04-11 14:59:15adminsetgithub: 81161
2019-05-21 14:08:19skypicklesetmessages: + msg343051
2019-05-21 14:08:11skypicklesetmessages: + msg343050
2019-05-21 01:16:19josh.rsetnosy: + josh.r
messages: + msg342971
2019-05-21 01:15:40steven.dapranosetstatus: open -> closed

nosy: + steven.daprano
messages: + msg342969

resolution: not a bug
stage: resolved
2019-05-21 00:31:29skypicklecreate