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]
Related
I am trying to understand chain assignment in Python.
If I run x = x[1] = [1, 2], I get an infinite list [1, [...]].
But if I run x = x[1:] = [1, 2], I will get a normal list [1, 1, 2].
How does it work in the background to make these two different results?
First, understand that in a chained assignment, the right-most expression is evaluated to an object. A reference to that object is then assigned to each target in sequence, from left to right. x = y = z is effectively the same as
t = z # A new temporary variable "t" to hold the result of evaluating z
x = t
y = t
del t
Second, you need to understand the difference between assigning t to the subscription x[1] and to the slicing x[1:]. In the former, element 1 of x is replaced by t. In the latter, elements x[1], x[2], etc are replaced by t[0], t[1], etc, extending or shrinking x as necessary.
The first example is equivalent to
t = [1,2]
x = t
x[1] = t
del t
x[1] becomes a reference to x itself; list.__repr__ can detect cycles like this and represents them with .... x, x[1], x[1][1], etc are all references to the original list [1,2].
The second example is equivalent to
t = [1,2]
x = t
x[1:] = t
del t
In this case, no cycles are created. x[1] is replaced by a reference to 1, not x/t itself, and x is extended to make x[2] a reference to 2. (If it helps, think of x[1:] = t as producing the same result as x = [x[0]]; x.extend(t).)
x = x[1] = [1, 2]
In this case, x and x[1] are the same object. Therefore x contains itself, which is represented by ... in your output.
x = x[1:] = [1, 2]
In this case, x is a slice, and slices are copies. so they are not the same object.
Explaining the outputs:
x = x[1] = [1, 2] # Output: [1, [...]]
The reason is that the list is not copied, but referenced.
x[1] is a reference to the list [1, 2].
When you assign x[1] to x, you are assigning a reference to the list [1, 2] to x.
So x and x[1] are both references to the same list.
When you change the list through one of the references, the change appears in the other reference as well. This is called aliasing.
x = x[1:] = [1, 2] # Output: [1, 1, 2]
The reason is that the list is copied, not referenced.
x[1:] is slice of the list [1, 2].
The slice is a copy of the list, not a reference to the list.
When you assign x[1:] to x, you are assigning a copy of the list [1, 2] to x.
So x and x[1:] are both copies of the same list.
When you change the list through one of the references, the change does not appear in the other reference.
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]
This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 3 years ago.
I am not understanding the difference between these two methods can someone explain? Why in one of them the reference object was changed but in the second one the reference remains unchanged? I come from Java, C# background if that helps. To me it seems like the reference should update in both of them. Thanks
def changeme( mylist ):
"This changes a passed list into this function"
mylist.append([1,2,3,4]);
print "Values inside the function: ", mylist
return
# Now you can call changeme function
mylist = [10,20,30];
changeme( mylist );
print "Values outside the function: ", mylist
Values inside the function: [10, 20, 30, [1, 2, 3, 4]]
Values outside the function: [10, 20, 30, [1, 2, 3, 4]]
def changeme( mylist ):
"This changes a passed list into this function"
mylist = [1,2,3,4]; # This would assig new reference in mylist
print "Values inside the function: ", mylist
return
# Now you can call changeme function
mylist = [10,20,30];
changeme( mylist );
print "Values outside the function: ", mylist
Values inside the function: [1, 2, 3, 4]
Values outside the function: [10, 20, 30]
mylist = [1,2,3,4] in the function changes the value of the parameter mylist, which is a copy of the reference to mylist outside of the function. The original reference does not change.
If you want to modify the list (rather than a reference to it), use mylist[:] = [1,2,3,4].
This is one of the most important things you will learn about Python (well, it was for me).
Every object is inherently unnamed but you can bind a variable name to it.
When you do:
x = [1, 2, 3]
two things happen:
the [1, 2, 3] object is created; and
the x name is bound to it.
That's why, when you change an object, all bound names seem to change:
>>> x = [1, 2, 3] ; y = x ; x ; y
[1, 2, 3]
[1, 2, 3]
>>> x.append(42) ; x ; y
[1, 2, 3, 42]
[1, 2, 3, 42]
You're not changing x or y in that case, you're changing the object behind those variables and, since both variables are bound to that object, both will be affected.
So, how does that affect your question. When you pass a variable to a function, the name in the function definition is simply bound to the same object as the one you passed in:
def modList(x): # 'x' and 'actual' now point to the SAME object.
x.append(42) # this will modify that object, no binding changes.
x = [4, 5, 6] # this will create NEW object and rebind 'x' to it.
actual = [1, 2, 3] # object is [1, 2, 3], 'actual' is bound to it.
modList(actual) # pass object reference to function.
So statements that change the object (like the first line of modList) will change it for all bindings whereas statements that rebind (like the second line) will lose access to the original object.
If you want to change the object with an assignment-looking statement, you can use array slicing to do so. This works because you're changing the elements in the object rather than rebinding x to a new object:
x[:] = [4, 5, 6]
I can't seem to understand following behavior in python:
x = [0, [1,2,3,4,5],[6]]
y = list(x)
y[0] = 10
y[2][0] = 7
print x
print y
It Outputs:
[0, [1, 2, 3, 4, 5], [7]]
[10, [1, 2, 3, 4, 5], [7]]
Why is second index of x and y updated and only the first index of y?
This happens because list(x) creates a shallow copy of the list x. Some of the elements in x are lists themselves. No copies are created for them; they are instead passed as references. In this way x and y end up having a reference to the same list as an element.
If you want to create a deep copy of x (i.e. to also copy the sublists) use:
import copy
y = copy.deepcopy(x)
In Python,sequence are divided into mutable sequence,which can be changed after they are created, and immutable sequence.For immutable sequences(string,Unicode,Tuples),Python make a copy for them.For mutable sequences(Lists,Byte Arrays), Python make a reference for them.
So if you change x ,y will also be changed as as they have a reference to the same list.
The standard type hierarchy
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.