Query regarding call by value and call by reference in Python - python

I am quite confused, which case is correct, first case or second case or both the cases are correct in Python?
Why in first case the list x remain unchanged?
why in second case the list x also got modified?
Case 1:
x = [0, 1, 2]
def cleanIt(y):
y = []
return y
print(cleanIt(x)) #[]
print(x) #[0, 1, 2]
Case 2:
x = [0, 1, 2]
def appendOne(z):
z.append(4)
return z
print(appendOne(x)) #[0, 1, 2, 4]
print(x) #[0, 1, 2, 4]

Python behaves like pass-by-object-reference. Therefore what you really pass into the functions are actually references to the objects you give. In the first example, you pass a reference that points to the memory address in which x is present. However when you do the assignment operation y = [], you simply change the memory address your reference points to. You do not change the content of x. In the second example, you call the method append(). In that case, you call this method for the object that is present in the memory address pointed to by your reference.
These may come confusing at the beginning, but you should remember that assigning your variable directly to something else doesn't actually change your variable, it changes where your reference points to. However using a method, modifying your variable updates it.

I have used id() to give you a clear idea of the references. id() returns the memory address of the argument passed to it.
Case-1
x = [0, 1, 2]
print(id(x))
def cleanIt(y):
print(id(y))
y = []
print(id(y))
return y
print(cleanIt(x))
print(x)
Output:
139677941202304
139677941202304
139677940574528
[]
[0, 1, 2]
You pass the reference of x to cleanIt.
y = [] - Creates a new list altogether. This has nothing to do with x. See the references above.
return y - You return the newly created list i.e, y = []
Since x is unchanged, it will retain its initial values.
Case-2
x = [0, 1, 2]
print(id(x))
def appendOne(z):
print(id(z))
z.append(4)
print(id(z))
return z
print(appendOne(x))
print(x)
Output
139677940574912
139677940574912
139677940574912
[0, 1, 2, 4]
[0, 1, 2, 4]
You pass the reference of x to appendOne(). x and z point to the same list.
z.append(4) - Appends a 4 to list referenced by z.
Since z and x refer to the same list, changes made in z reflects in x too. See the references above.
In Python values are sent to functions by means of object reference.
Please read this to know more about this concept.
Edit:
If I want to have x unchanged while z should be [0,1,2,4], then how should I modify the python code?
This is how you can do.
x = [0, 1, 2]
print(id(x))
def appendOne(z):
print(id(z))
# Creates a new list with contents of x.
z = list(z)
z.append(4)
print(id(z))
return z
print(appendOne(x))
print(x)
Output
140368975642112
140368975642112
140368975011392
[0, 1, 2, 4]
[0, 1, 2]

Related

Local Variable Referenced before Assignment - Python

x = 2
y = 3
mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
print(mylist)
UnboundLocalError Traceback (most recent call
last) in
3
4
----> 5 mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
6
7 print(mylist)
in (.0)
3
4
----> 5 mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
6
7 print(mylist)
UnboundLocalError: local variable 'y' referenced before assignment
I do not understand what is wrong here and how to correct it.
This is because the CPython implementation of an inner comprehension in a multi-level comprehension is to make it a separate function, which has its own scope. By making y a left-value with its use as an iteration variable, y is determined to be a local variable to the separate function by the Python compiler, and as such the y in the range(y+1) cannot be referenced because the local y is not yet assigned a value in that function scope.
You can either rename the local y in the comprehension:
mylist = [[x, z] for x in range(x + 1) for z in range(y + 1)]
or use a non-comprehension solution instead:
mylist = []
for x in range(x + 1):
for y in range(y + 1):
mylist.append([x, y])
Normally, you see this error when you call a function that references a name in the local scope before assigning to it. As it happens, the outermost scope in a list comprehension works the same way in Python 3. This is a change from python 2, where loop variables in a comprehension would pollute the outer namespace.
In this particular case, using x and y as loop variables tells the interpreter that these are local variables. However, range(x + 1) and range(y + 1) attempt to read these variables before they are assigned. Python has an optimization to read variables that are determined to be local to a function (or comprehension) namespace from a special table rather than using normal LEGB lookup order. That is why you get the error instead of using the value from the outer scope.
A side-effect of this process is that the last value of x and y will not change the values that you assigned in the outer scope.
To fix this issue, ensure that the variables that define the range do not have the same name as the loop variables.
Try this:
x = 2
y = 3
mylist = [[j,k] for j in range(x+1) for k in range(y+1)]
print(mylist)
Prints out:
[[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3], [2, 0], [2, 1], [2, 2], [2, 3]]
As #Tim Roberts pointed out in his comment you are using x and y «for two conflicting purposes» in your list comprehension. You need to give unique names to each of them in the for loop, e.g. y -> k.
Have further further look at this SO post to learn more about nested list comprehensions.

What range function does to a Python list?

I am not able to figure out what is happening here. Appending reference to range function is kind of creating a recursive list at index 3.
>>> x = range(3)
[0, 1, 2]
>>> x.append(x)
[0, 1, 2, [...]]
>>> x[3][3][3][3][0] = 5
[5, 1, 2, [...]]
Whereas, when I try this:
>>> x = range(3)
[0, 1, 2]
>>> x.append(range(3))
[0, 1, 2, [0, 1, 2]]
I can easily deduce the reason for the second case but not able to understand what appending reference to range function is doing to the list appended.
In python2, ranges are lists.
lists, and most of the things in python are objects with identities.
li = [0,1]
li[1] = li # [0, [...]]
# ^----v
id(li) # 2146307756
id(li[1]) # 2146307756
Since you're putting the list inside itself, you're creating a recursive data structure.
First this is strange, and you probably should not use this in practice. The issue is not specific to range function and has to do with references. When you call x.append(x), you essentially say that x[-1] is x. So when you modify x[0], you also modify x[-1][0], x[-1][-1][0] etc.
To see that this is not range specific, you could use copy.copy:
from copy import copy
x = range(1)
x.append(x) # here x[1] is reference to x itself (same object)
print(x[0], x[1][0], x[1][1][0])
x[0] = 1
print(x[0], x[1][0], x[1][1][0]) # all values change
#
x = range(1)
x.append(copy(x)) # x[1] is a copy of x at previous state (new object)
print(x[0], x[1][0]) # cannot call x[1][1][0] -> x[1][1] is an int
x[0] = 1
print(x[0], x[1][0]) # only the first value changes
Output:
(0, 0, 0)
(1, 1, 1)
(0, 0)
(1, 0)

Function parameter is reference? [duplicate]

consider the following code:
>>> x = y = [1, 2, 3, 4]
>>> x += [4]
>>> x
[1, 2, 3, 4, 4]
>>> y
[1, 2, 3, 4, 4]
and then consider this:
>>> x = y = [1, 2, 3, 4]
>>> x = x + [4]
>>> x
[1, 2, 3, 4, 4]
>>> y
[1, 2, 3, 4]
Why is there a difference these two?
(And yes, I tried searching for this).
__iadd__ mutates the list, whereas __add__ returns a new list, as demonstrated.
An expression of x += y first tries to call __iadd__ and, failing that, calls __add__ followed an assignment (see Sven's comment for a minor correction). Since list has __iadd__ then it does this little bit of mutation magic.
The first mutates the list, and the second rebinds the name.
1)'+=' calls in-place add i.e iadd method. This method takes two parameters, but makes the change in-place, modifying the contents of the first parameter (i.e x is modified). Since both x and y point to same Pyobject they both are same.
2)Whereas x = x + [4] calls the add mehtod(x.add([4])) and instead of changing or adding values in-place it creates a new list to which a points to now and y still pointing to the old_list.

Why is this happening in Python ? Is it do with list mutability?

Please don't be harsh if my question is very simple or obvious.
Am a Python newbie, so just started out.
Actually this was a piece of code I came across on this Stack Overflow only but could not find an answer for why this is happening, so decided to ask it myself.
I wrote the below two programs :
1)
x=[1,2,3]
y=x
print x
y=y+[3,2,1]
print x
Output:
[1,2,3]
[1,2,3]
2)
x=[1,2,3]
y=x
print x
y+=[3,2,1]
print x
Output:
[1,2,3]
[1,2,3,3,2,1]
I can't understand why the two outputs are different in this case ?
is y=y+(something) not the same as y+=(something)
what is it that I am missing here ?
Thanks a lot for helping me out on this
This has nothing to do with mutability. It has to do with list objects and references to it.
When you do:
y=x
You are essentially making y refer to the same list that x is referring to.
Now,
y=y+[3,2,1]
^-------^ - Create a **new** List is that is equal to concatenation of `y` and [1,2,3]
^---^ - Bind variable `y` to this **new** list.
- The original list which `x` refered to -- is still *intact*
Again,
y+=[3,2,1]
^-------^ - Append [1,2,3] **in-place**.
- Since even `x` is pointing to the same list, it gets modified.
y = y+something will replace the contents of y, while y+=something will modify the list in-place.
TL;DR;
Code explanation:
>>> x = [1, 2]
>>> y = x
>>> id(y) == id(x)
True
>>> y += [3]
>>> id(y) == id(x)
True
>>> y = y + [3]
>>> id(y) == id(x)
False
Here's a little explanation of what's going on in your code.\
First code
You declared x:
x=[1,2,3]
Then you point y to the value of x:
y=x
After that, here's the tricky part:
y=y+[3,2,1]
This creates a new y variable, replacing the old one. y is no longer related to x. Now, x has it's own place in memory, and y has a separate one
Second code
Declare x:
x=[1,2,3]
Points y to the value of x:
y=x
And lastly:
y+=[3,2,1]
This modifies the list pointed by y in-place. In other words, you're modifying x as well:
If this isn't clear enough, just comment:)
Hope this helps!
After the line
y = x
Both x and y point to the same object. In way that operators are overloadable in Python:
y += [3, 2, 1]
Is calling function
y.__iadd__([3, 2, 1])
Which in turn adds the elements of [3, 2, 1] to a list pointed by y - which happens to be the same list pointed by x.
On the other hand:
y = y + [3, 2, 1]
Is the same as:
y = y.__add__([3, 2, 1])
Which, in turn, creates a new list containing combination of both and assign it to y. After it y and x points to different objects and the one pointed by x is not modified.
No, it's not the same. In fact this was one of the reasons why it took a long time for Python to get +=, in the beginning people thought it would be too confusing to add.
y = y + [3, 2, 1]
Rebinds y to a new value, the one that is the result of the expression y + [3, 2, 1].
But
y += [3, 2, 1]
changes y, if it currently refers to something that can be changed. And lists can.
Since the list that y refers to is changed, and x refers to the same list, printing x also shows the new value.
If y were immutable (like a tuple), then it doesn't work that way:
x = y = (1, 2, 3)
y += (3, 2, 1)
print(x) # prints (1, 2, 3)
print(y) # prints (1, 2, 3, 3, 2, 1)
The reason is that in the case of mutable objects, it's more likely that changing the object is what you actually want, so they made it work that way. Immutable object can't work that way, so they don't.
In python every time you use the operator = you "delete" the old version and create a new one.
Thus y = y + something will create a new variable of y that isn't the same as the old y.
While y+=something will add something to the current value in y.

Python list reference in a function.

I'm having trouble understanding the odd behaviour in python functions if i pass in a list.
I made the following functions:
def func(x):
y = [4, 5, 6]
x = y
def funcsecond(x):
y = [4, 5, 6]
x[1] = y[1]
x = [1, 2, 3]
When i call func(x) and then print out x , it prints out [1, 2, 3], just the way x was before, it doesnt assign the list y to x. However, if i call funcsecond(x), it assigns 5 to the second position of x. Why is that so? When i assign the whole list, it doesn't do anything, but when i assign only one element, it changes the list i originally called it with.
Thank you very much and i hope you understand what im intending to say , i'm having a hard time expressing myself in English.
The former rebinds the name, the latter mutates the object. Changes to the name only exist in local scope, whereas a mutated object remains mutated after the scope is exited.
this is happening beacuse x points to a object which is mutable.
def func(x): # here x is a local variable which refers to the object[1,2,3]
y = [4, 5, 6]
x = y #now the local variable x refers to the object [4,5,6]
def funcsecond(x): # here x is a local variable which refers to the object[1,2,3]
y = [4, 5, 6]
x[1] = y[1] # it means [1,2,3][1]=5 , means you changed the object x was pointing to
x = [1, 2, 3]

Categories