This is the code I type. Here the value of int has not changed outside of function but value of list has changed.I was expecting the value of list will not change. What is the reason?
>>> def p1(list1):
list1[0]=1000
>>> def p2(i):
i+=10
>>> li=[1,2,3,4,5]
>>> int1=10
>>> p1(li)
>>> p2(int1)
>>> li
[1000, 2, 3, 4, 5]
>>> int1
10
Note that in p1, you are not assigning a value to list1, which is the name of a variable local to p1. You are actually assigning to the first element of the list object referenced by list1, which is the same list object referenced by li in the enclosing scope.
In p2, on the other hand, i+=10 does assign a value to the local variable i, not the variable int1 in the enclosing scope. This is because the += operator on objects of type int do not actually modify the object, but return a new object instead. (That is, for an int, i+=10 is equivalent to i = i + 10.)
Just to show that += can operate on the underlying object directly, consider this function:
def p3(list1):
list1 += [10]
Then run it on a list:
>>> foo = [1,2,3]
>>> p3(list1)
>>> foo
[1, 2, 3, 10]
Here, the call list1 += [10] is really equivalent to list1.extend([10]), not list1 = list1 + [10], due to how list.__iadd__ is defined. Here, you are again not assigning a value to the name list1, but rather invoking a method on the object referenced by list1 (which is the same object referenced by foo).
(Update: as pointed out by user2357112, technically you do assign to list1, but list.__iadd__ is designed to properly assign the same list back, so that the end result is that you still have a reference to the same mutable object you started with.)
Assigning to a variable, like i+=10, is an operation on a variable, while index assignment, like list1[0]=1000, is an operation on the object a variable refers to.
When you call p2(int1), the local variable i gets set to refer to the int int1 referred to, and then the i+=10 statement redirects i to refer to a new, larger integer. int1 still refers to the old integer.
When you call p1(li), the local variable list1 gets set to refer to the list li referred to, and then the list1[0]=1000 statement sets the first cell of the list to refer to 1000. li still points to this list throughout the modification, and after the function returns, li still reflects the change.
(There's another complexity here in that += also requests that an object mutate itself if it's mutable, in addition to performing an assignment, but that doesn't come up in this code.)
A list is like a pointer to a list object.
#For example
a =[0,0]
b = a
b[0]=1
resulting in
b
[1,0]
Where as an int is an a different beast
#For Example
a = 0
b = a
b +=1
resulting in
a
1
to get the int behaviour from a listyou can make a clone of a list by passing it to list()
#for example
a = [0,0]
b = list(a)
b[0]=1
resulting in
a
[0,0]
and if you want the list behaviour from an int I guess you would have to just put it in a list.
Related
Suppose I have function with list parameter, and inside its body I want to modify passed list, by copying elements of an array to the list:
def function1 (list_arg):
a = function2() #function2 returns an array of numbers
list_arg = list(a)
list1 = [0] * 5
function1(list1)
list1
[0,0,0,0,0]
When doing it like this, it doesn't work. After executing function1(list1), list1 remains unchanged. So, how to make function1 return list1 with the same elements (numbers) as array a?
If you assign something to the variable list_arg, it will from then on point to the new value. The value it pointed to before that assignment (your original list) will stay unchanged.
If you, instead, assign something to elements of that list, this will change the original list:
list_arg[:] = list(a)
This will make your code work as you wanted it.
But keep in mind that in-place changes are hard to understand and probably can confuse the next developer who has to maintain your code.
What I think you are asking is why after calling f(a), when f re-assigns the a you passed, a is still the "old" a you passed.
The reason for this is how Python treats variables and pass them to functions. They are passed by reference, but the reference is passed by value (meaning that a copy is created). This means that the reference you have inside f is actually a copy of the reference you passed. This again implies that if you reassign the variable inside the function. It is a local variable existing only inside the function; re-assigning it won't change anything in outside scopes.
Now, if you rather than reassigning the local variable/reference inside f (which won't work, since it's a copy) perform mutable operations on it, such as append(), the list you pass will have changed after f is done.
See also the question How do I pass a variable by reference? which treats the problem and possible solutions in further detail.
TL;DR: Reassigning a variable inside a function won't change the variable you passed as an argument outside the function. Performing mutable operations on the variable, however, will change it.
You can operate on the list to change its values (eg, append something to it, or set its values) but changes will be reflected outside of the function only if you operate on the reference to the passed in object:
def function1 (list_arg):
list_arg.append(5)
If you have questions when doing this, print out the ids:
def function1 (list_arg):
print 1, id(list_arg)
list_arg[:] = ["a", "b", "c"]
print 2, id(list_arg)
list_arg = range(10)
print 3, id(list_arg)
x = [1,2,3]
function1(x)
print x
prints:
1 4348413856
2 4348413856
3 4348411984
['a', 'b', 'c']
That is, x is changed in place, but assigning to the function's local variable list_arg has no impact on x, because is then just assigns a different object to list_arg.
You're changing a reference to a local variable. When you pass in list_arg this way:
def function1 (list_arg):
list_arg is a reference to an underlying list object. When you do this:
list_arg = list(a)
You're changing what list_arg means within the function. Since the function exits right after that, list_arg = list(a) has no effect.
If you want to actually change the reference to the list you have to do assign it to the result of the function.
def function1 ():
'a = some array'
return list(a)
list1 = [0] * 5
list1 = function1()
Or you could modify the contents of the list without changing the reference.
def function1(list_arg):
del list_arg[:] # Clears the array
'a = some array'
list_arg.extend(a)
This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 4 years ago.
It is believed that lists in python as a mutable object are passed by reference. However when I try to change the list passed to a function to be assigned by another list, particularly empty list, the change is not observable in the caller.
Giving more details here is my code:
def callee(l):
l = list() # this could be any list but my interest was empty list
def caller():
l = [1, 2, 3]
caller(l)
print(l) # prints [1, 2, 3] while I expect an empty list
How can I remove all elements of a list in the callee function?
When I change one particular element of a list in the callee, it is observable in the caller. What kind of passing is this, call-by-value or call-by-reference? I believe neither and I appreciate any clarification on this too.
You are just reassigning a local variable in the scope of callee.
So, just change the content of that list instead:
def callee(l):
l[:] = list()
# or l.clear()
You're changing the reference, instead of modifying the object referenced to. To see a difference, you need to actually change the object you pass by reference:
def callee(l):
while len(l) > 0:
l.pop()
def caller():
l = [1, 2, 3]
callee(l)
print(l) # prints []
Each time you use the = operator you are changing the object the variable is pointing to.
When you type
l = [1, 2, 3]
A python list is created in memory, and a reference in the scope of caller() named l is now pointing to this newly created list.
However, when the callee() function is called, another list is created in memory and now it has one reference in the scope of callee() pointing to it.
So now we have 2 lists in memory (and not one) each with its own reference pointer (named l in both cases but in different scopes). When callee() returns, the reference to the second list expires and the object now has no more pointers pointing to it, so the garbage collector deletes it from memory. The original list is left unchanged.
To clear the list up, you can use l.clear(). Since you didn't use the = operator, no new object is created in memory and you can be sure that you are pointing to the same old object.
I am new to python and was going through the python3 docs. In python strings are said to be immutable, then how is this possible:
if __name__ == '__main__':
l = 'string'
print(l)
l = l[:2]
print(l)
returns this output:
string
st
Informally, l now points to a new immutable string, which is a copy of a part of the old one.
What you cannot do is modify a string in place.
a = "hello"
a[0] = "b" # not allowed to modify the string `a` is referencing; raises TypeError
print(a) # not reached, since we got an exception at the previous line
but you can do this:
a = "hello"
a = "b" + a[1:] # ok, since we're making a new copy
print(a) # prints "bello"
The key to understanding this problem is to realize that the variable in Python is just a "pointer" pointing to an underlying object. And you confused the concept of immutable object and immutable variable(which does not exist in Python).
For instance, in your case, l was initially a pointer pointing to a str object with content "string". But later, you "redirect" it to a new str object whose content is "st". Note that when the program runs to the line l = l[:2], it's the pointer being modified, not the object pointed to by the pointer. If you wish, you can also "redirect" l to another object with type other than str, say l = 123. Just remember, the original object pointed to by l(str "string") is not modified at all, it's still there in the memory (before it is garbage-collected), but just no longer pointed to by l.
For you to better understand the concept of immutable object, let's look at a mutable object. For example, list in Python is mutable.
l = [1, 2, 3] # a new list with three elements
l.append(4) # append a new element 4 to the list (l is now modified!!!)
In the code above, we modified l by appending a new element to it. Throughout the program, l points to the same object, but it's the object pointed to by l that is changed in the process.
Strings themselves are immutable, but the variables can be bound to anything.
The id() method checks the "id" of an object. See below.
>>> l = 'string'
>>> print(l, id(l))
string 2257903593544
>>> l = l[:2]
>>> print(l, id(l))
st 2257912916040
l was bounded to a new immutable object which only contains "st".
I am still a bit confused about how arguments are passed in python.
I thought non-primitive types are passed by reference, but why does the following code not print [1] then?
def listTest(L):
L = L + [1]
def main:
l = []
listTest(l)
print l #prints []
and how could I make it work.
I guess I need to pass "a pointer to L" by reference
In listTest() you are rebinding L to a new list object; L + [1] creates a new object that you then assign to L. This leaves the original list object that L referenced before untouched.
You need to manipulate the list object referenced by L directly by calling methods on it, such as list.append():
def listTest(L):
L.append(1)
or you could use list.extend():
def listTest(L):
L.extend([1])
or you could use in-place assignment, which gives mutable types the opportunity to alter the object in-place:
def listTest(L):
L += [1]
>>> def test():
... a.remove(1)
>>> a = [1,2]
>>> test()
>>> print a
[2]
Why does a equal [2] rather than [1,2]?
List is mutable. If you pass it to a function, and the function changes it, it stays changed.
Use an immutable structure: tuple: a = (1,2)
Pass a copy of original list: b = list(a); b.remove(1) — now a and b have different contents, a hasn't changed.
Also, try not to use mutable global data. Either pass a to the function, or have a as an attribute of an object, and the function as its method.
It's not clear what you want. Your test() function modifies the global 'a' list, so it's unsurprising that 'a' gets modified.
If you want 'test' to work on a copy of a instead directly on a, you may copy it first.
For example,
def test():
a2 = list(a)
a2.remove(1)
Lists are mutable objects, they are meant to be changed. If you want to forbid changes, convert it to a tuple (e.g. a = (1, 2)) instead. Tuples are immutable, so it's not possible to change them without copying and re-assigning the variable.
Because the list a exists in the global namespace and when you call a remove on it, the value 1 is removed.
If you don't want it to be modified, simply create a new list. If you call remove on the list a, of course it going to remove the value.