Please help me as I am new to python
when I call this function the original value of list1 changes
def mystery(list1):
list1[0] , list1[1] = list1[1], list1[0]
list1 = [7,82,44,23,11]
mystery(list1)
print(list1) #prints [82, 7, 44, 23, 11]
how it can change the value of global list1
if I change my function to
def mystery(list1):
list1 = list1 + list1[2:5]
then I am getting the global value of list1 and not the updated one.
lists are mutable objects in python, so if you are passing to a function a list all the changes that you make to the list inside your function will be reflected everywhere/globally
If you pass a mutable object into a method, the method gets a reference to that same object and you can mutate it to your heart's delight, but if you rebind the reference in the method, the outer scope will know nothing about it, and after you're done, the outer reference will still point at the original object.
If you pass an immutable object to a method, you still can't rebind the outer reference, and you can't even mutate the object.[more details here]
The reason list1 is changing is because you're actually modifying the list object inside of the function.
It really has nothing todo with global / local variables, if you renamed the parameter inside your function to something else, and still passed in list1, list1 would be modified.
If you're wanting to return a new list, you need to first create a copy of the list, there's many ways to do this. list(list1) is one way. Then return the list at the end of the function.
If I understand your queston, you want to actually append some more values to the passed in list, use list1.append(...) to add to the end of the list.
And since you're modifying the list itself, it'll change in the larger scope.
But it's still not using the global scope, as you're just using a var with the same name in the local scope.
Maybe just return the list?
def mystery(list1):
return list1 + list1[2:5]
list1 = [7,82,44,23,11]
list1 = mystery(list1)
print(list1)
In python, assigning a value to a variable like a = 5 means you are creating an object in the memory for the value 5. Now a is a link to that object holding 5 (in Layman's terms). If you change a now, say a = "something else", this creates a new object for the string "something else" in somewhere else in the memory and now a is pointing to that. This is why you get to change the data type of python variables.
When you pass something to a python function, that link is the one that is passed on. So when you call mystery(list1), original link to the list1 is passed. Therefore changing an element in the list1 means you are assigning a new value to that particular element in the original list1. No matter you are inside or outside of the function mystery() in this case since you will be using the original link you created to access the element inside list1 to change it. The change happens inside the list1; list1 didn't get reassigned.
However when you do list1 = "something new" inside your function mystery(), you are creating a new variable list1 inside the function which is local to the function. You are not changing the original list1.
Passing mutable objects into a function and then modifying the object inside the function will have the same effect as modifying the object directly.
list1 = [1,2,3]
def func(list1):
list1[0] = 5
>>>list1
[5,2,3]
This is effectively same as directly running list1[0] = 5
However if you pass an immutable object into a function such as tuple, It will not support item assignement TypeError: 'tuple' object does not support item assignment. So you need to build a new immutable object and return it.
>>> tup1 = (1,2,3) # doing tup1[0] = 5 will cause TypeError
>>> tup2 = tup1 + (5,)
>>> tup2
(1, 2, 3, 5)
Put it in function,
>>> def func2(tup1):
return tup1 + (5,)
>>> func2(tup1=(1,2,3))
(1, 2, 3, 5)
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)
I think that the variable length argument turns into a tuple when it enters the edit_list function, so I changed it from a Tuple to a List to edit it. When it returns, I assume it is still treated as a Tuple and therefore no changes to the argument values can be returned?
If so, how would I go about editing the contents of a list that is used in a variable length argument context?
def main():
x = ['hi','hello','world',1,2]
edit_list(*x)
print(x)
#why can't I change the list index 1 value to '2' and then
#return the modified arg list to main and then print it out?
def edit_list(*args):
args = list(args)
print(args)
args[1] = 2
return args
if __name__ == '__main__' : main()
You would need to pass in the list directly, instead of unpacking the list using edit_list(*x).
def edit_list(my_list):
my_list[1] = 2
def main():
x = [ ... ]
edit_list(x)
print(x)
To understand the mechanism of this, you should try to be familiar with the concept "mutable" and "immutable". (of course, only if you want to be better at Python).
Take your code as an example, if the element you passed in is a list, and you are changing the element of that list, you will get a different result.
def main():
x = ['hi',['hello'],'world',1,2]
edit_list(*x)
print(x)
# After you run edit_list, the original x will be changed
def edit_list(*args):
args = list(args)
print(args)
args[1][0] = 2
return args
In python, objects are either "mutable" or "immutable". lists for example, are mutable. integers, strings are immutable. When you pass a mutable object to a function, you pass in it's reference so when you edit it(not assigning another object to the variable) you will edit the original object. However, if it's immutable, when you pass in the object to the function, it will generate a copy.
Also use your code as an example. You are effectively doing
edit_list("hi", "hello", "world", 1, 2)
All the arguments are immutable, so you copied each of them and give them to the function. Therefore, when you are inside the function, you already have different objects to the original. There's nothing you can do to change the original list.
However, if you pass them in as a list
edit_list(x)
Because x is a mutable object, you pass the reference, or the original object in the function. So when you edit the list lst[1] = 2, the original one will change. However, if you do something like lst = [1, 2, 3], the original x won't be changed, because you just created another object and assigned to the unrelated variable lst.
I am using a .pop method on a Global list inside a function block, but the Global list is being updated outside the block. I thought that local variables can't modify Global variables.
This should not work, but it does:
import random
PhraseBank = ['a','b','c','d']
def getPuzzle(secretphrase):
phraseIndex = random.randint(0,len(PhraseBank)-1)
secretphrase = PhraseBank.pop(phraseIndex)
return secretphrase #Returns and item indexed from the PhraseBank
while len(PhraseBank) != 0:
secretphrase = getPuzzle(PhraseBank) #works
print(secretphrase, PhraseBank)
OUTPUT is:
a ['b', 'c', 'd']
d ['b', 'c']
c ['b']
b []
Why is PhraseBank getting updated Globally when I am only modifying it inside a function block?
Lists are mutable. You are changing the list that PhraseBank refers to, but it's still referring to the same list. So the variable isn't changed (still refers to the same thing) but that thing has changed.
If you were assigning a value to the PhraseBank, it would not be changed unless you say you are using the global variable explicitly. However, each var in Python is just a named reference to an object. You read the var and then modify the object it refers to. Yes, you can't change the reference itself, but you can change the object.
So, what you faced is one of the most typical features of Python. Everything is an object, all the variables are references. Understanding that fact often helps to understand many things that may seem strange. A good example:
>>> li = [1]
>>> a = (li, li)
>>> a[0].append(1)
>>> a[1]
[1, 1]
>>> li
[1, 1]
>>> li.append(1)
>>> a
([1, 1, 1], [1, 1, 1])
If nothing surprises you in the behavior of the code above, then you understand how are the variables and objects related. :-) Here the variables that are not touched are changed, but not because they start referring other objects, but because the objects they refer to are modified. So do the tuples that are immutable. Yes, they are. A tuple, once created, always refers to the same objects. But each of those object may be changed.
You can't assign to a global variable inside a function (unless you explicitly use a global declaration). You can modify objects stored in global variables just fine.
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)
I tried the following code on Python, and this is what I got:
It seems like for many changes I try to make to the iterables by changing elem, it doesn't work.
lis = [1,2,3,4,5]
for elem in lis:
elem = 3
print lis
[1, 2, 3, 4, 5]
However if the iterables are objects with its own methods (like a list), they can be modified in a for loop.
lis = [[1],[2]]
for elem in lis:
elem.append(8)
print lis
[[1, 8], [2, 8]]
In the for loop what exactly is the 'elem' term? Thanks in advance!
The reason that this doesn't work is because you're misunderstanding what elem is. It's not the object itself, and it's not even correct to call it a "variable".
It's a name, kind of like a label, that points to the object. If you just directly assign over it, you're just overwriting the name to point at something else. But, you still have the original reference to the list, so assigning a different value over elem doesn't modify lis itself.
Now, in this case, since all of the objects that elem points to are integers, you can't even change them at all - because integers (and many other types, like strings or tuples) are immutable. That means, simply put, that once the object has been created it cannot be modified. It has nothing to do with whether they "have methods" or not (all Python objects have methods, integers included), but on whether or not they are immutable.
Some objects, however, are mutable, meaning that they can be changed. Lists are examples of such objects. In your second example, elem is a name that references the list objects contained within lis, which themselves are mutable. That is why modifying them in-place (using .append(), or .remove(), etc) works fine.
The elem variable in your for loop is a reference to the current object on each iteration. Changing it won't do anything; it will just change the value of the variable elem and that's going to be changed the next time through the loop anyway. To actually change the value of the element in the list, you need a reference to the list and the index of the element to be changed, and you don't have the latter.
So what you want to do is something like this:
for index, elem in enumerate(lis):
lis[index] = 3
This way you have elem for the element's value and index for the position in the list. It saves you from writing lis[index] constantly to get values, but you must still do so to change elements.
You can also do:
for index in xrange(len(lis)):
lis[index] = 3
However, in most situations this is considered un-Pythonic (among other things, what happens if the list gets longer or shorter while it's being iterated)?
Here, you are actually modifying the list object in your second example. In the first example, you are not modifying the number, you are replacing it. This can be a complicated nuance for new users of Python.
Check this out:
>>> x = 1
>>> id(x)
4351668456
>>> x = 2
>>> id(x)
4351668432
id returns the identifier of the object. As you can see above, the object of x changes both of these times.
>>> y = [1]
>>> id(y)
4353094216
>>> y.append(2)
>>> id(y)
4353094216
Here, I modify the list, so the list is still the original object y.
So, all this means that when you are doing elem = 3, it's not modifying it, it is replacing it. And by now, it's not associated with the list anymore.
This is one of the ways you could do what you are trying to do. This grabs the index and then modifies the list, not the number.
lis = [1,2,3,4,5]
for idx, elem in enumerate(lis):
lis[idx] = 3
print lis
[1, 2, 3, 4, 5]
When you assign a new value to the name elem, you just change the local binding in the for loop. If you want to change the values stored in lis, use map or a list comprehension, like this:
lis = [3 for elem in lis]
You can, however, modify the attributes of elem (or call methods that do so), just like you can on any other value.
In your first example you are trying to modify an integer, and it's inmutable (as strings are).
Python variables should be seen as labels pointing to an object. When you iterate over a list of inmutables, elem points to an inmutable object, not to that position in the list, so you can't modify the original list.
In the second case, elem point to an object that can be modified, so you see the original list changed.
It depends on what the type() of elem is.
In your first case each elem is an int object and it does work to change it. You are changing a temporary object when you say: elem = 3, not the item in the list itself.
In the second case each elem is a list object.