def mutation(input_list):
list_copy = input_list[:]
list_copy[0] = 10
input_list = list_copy
# Correctly mutates
sample_list = [0,1,2]
sample_copy = sample_list[:]
sample_copy[0] = 10
sample_list = sample_copy
print(sample_list)
# Incorrectly mutates
sample_list = [0,1,2]
mutation(sample_list)
print(sample_list)
In the top snippet of code, I've made a copy of a list and modified it. I then set the original to the copy and then it works.
What confuses me is why doing this process outside of a function works but if I were to do it inside a function (the 2nd snippet of code), it fails?
For reference, the code returns:
[10, 1, 2]
[0, 1, 2]
EDIT: I know that calling input_list[0] = 10 works. I just want to know what makes this different from what I showed above all in memory?
In mutation, input_list starts out pointing at the same object as sample_list, but later you make it point at list_copy.
sample_list is not modified. It is still pointing at the original object.
When you do it outside of the function you change sample_list to point to the new object before printing it.
I think that using the built-in function id to show the object ID will help here. If the ID of two variable names gives the same result then they refer to the same object; otherwise the objects are different.
>>> def mutation(input_list):
... print(id(input_list))
... list_copy = input_list[:]
... print(id(list_copy))
... input_list = list_copy
... print(id(input_list))
...
>>> a = list(range(10))
>>> print(id(a))
140737233394376
>>> mutation(a)
140737233394376
140737233289160
140737233289160
In the above, we see that after input_list = list_copy, the name input_list refers to identically the same object in memory as list_copy, which means it no longer refers to the list given as the function argument. This is why the mutation you expect does not work - you are modifying an entirely different object.
That's because you sets new value for input_list which is local variable of mutation function.
The simplest solution is changing value of first element of list passed as argument:
def mutation(input_list):
input_list[0] = 10
Otherwise you can write function which changes value of global variable called sample_list
def mutation():
global sample_list
list_copy = sample_list[:]
list_copy[0] = 10
sample_list = list_copy
Related
This is just a question asking for the difference in the code.
I have several lists ie. a=[], b=[], c=[], d=[]
Say if I have a code that appends to each list, and I want to reset all these lists to its original empty state, I created a function:
def reset_list():
del a[:]
del b[:]
del c[:]
del d[:]
So whenever I call reset_list() in a code, it removes all the appended items and set all lists to []. However, the one below doesn't work:
def reset_list():
a = []
b = []
c = []
d = []
This might be a stupid question but I was wondering why the second one wouldn't work.
When you do del a[:] then it looks for the variable a (including outer contexts) and then performs del found_a[:] on it.
But when you use a = [] it creates a name a in the current context and assigns an empty list to it. When the function exits the variable a from the function is not "accessible" anymore (destroyed).
So in short the first works because you change the a from an outer context, the second does not work because you don't modify the a from the outer context, you just create a new a name and temporarily (for the duration of the function) assigns an empty list to it.
There's a difference between del a[:] and a = []
Note that these actually do something different which becomes apparent if you have additional references (aliases) to the original list. (as noted by #juanpa.arrivillaga in the comments)
del list[:] deletes all elements in the list but doesn't create a new list, so the aliases are updated as well:
>>> list_1 = [1,2,3]
>>> alias_1 = list_1
>>> del alist_1[:]
>>> list_1
[]
>>> alias_1
[]
However a = [] creates a new list and assigns that to a:
>>> list_2 = [1,2,3]
>>> alias_2 = list_2
>>> list_2 = []
>>> list_2
[]
>>> alias_2
[1, 2, 3]
If you want a more extensive discussion about names and references in Python I can highly recommend Ned Batchelders blog post on "Facts and myths about Python names and values".
A better solution?
In most cases where you have multiple variables that belong together I would use a class for them. Then instead of reset you could simply create a new instance and work on that:
class FourLists:
def __init__(self):
self.a = []
self.b = []
self.c = []
self.d = []
Then you can create a new instance and work with the attributes of that instance:
>>> state = FourLists()
>>> state.a
[]
>>> state.b.append(10)
>>> state.b.extend([1,2,3])
>>> state.b
[10, 1, 2, 3]
Then if you want to reset the state you could simply create a new instance:
>>> new_state = FourLists()
>>> new_state.b
[]
You need to declare a,b,c,d as global if you want python to use the globally defined 'versions' of your variables. Otherwise, as pointed out in other answers, it will simply declare new local-scope 'versions'.
a = [1,2,3]
b = [1,2,3]
c = [1,2,3]
d = [1,2,3]
def reset_list():
global a,b,c,d
a = []
b = []
c = []
d = []
print(a,b,c,d)
reset_list()
print(a,b,c,d)
Outputs:
[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3]
[] [] [] []
As pointed out by #juanpa.arrivillaga, there is a difference between del a[:] and a = []. See this answer.
The 1st method works because:
reset_list() simply deletes the contents of the four lists. It works on the lists that you define outside the function, provided they are named the same. If you had a different name, you'd get an error:
e = [1,2,3,4]
def reset_list():
del a[:] #different name for list
NameError: name 'e' is not defined
The function will only have an effect if you initialize the lists before the function call. This is because you are not returning the lists back after the function call ends:
a = [1,2,3,4] #initialize before function definition
def reset_list():
del a[:]
reset_list() #function call to modify a
print(a)
#[]
By itself the function does not return anything:
print(reset_list())
#None
The 2nd method doesn't work because:
the reset_list() function creates 4 empty lists that are not pointing to the lists that may have been defined outside the function. Whatever happens inside the function stays inside(also called scope) and ends there unless you return the lists back at the end of the function call. The lists will be modified and returned only when the function is called. Make sure that you specify the arguments in reset_list(a,..) in the function definition:
#function definition
def reset_list(a):
a = []
return a
#initialize list after function call
a = [1,2,3,4]
print("Before function call:{}".format(a))
new_a = reset_list(a)
print("After function call:{}".format(new_a))
#Output:
Before function call:[1, 2, 3, 4]
After function call:[]
As you've seen, you should always return from a function to make sure that your function "does some work" on the lists and returns the result in the end.
The second function (with a = [ ] and so on) initialises 4 new lists with a local scope (within the function). It is not the same as deleting the contents of the list.
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)
I have a question about variable scope in Python. Why is a mutation of a variable allowed without returning the mutated variable?
def mutation(L):
L.append("x")
L = []
mutation(L)
print(L)
I would expect this to print [], as the mutation of L inside the function only affects the local scope. Why is "L" mutated even in the global scope?
Since Python is pass by object reference, when you pass something to a function it points to the same object in memory that you can manipulate. If you want a function to not modify the original list, you need to make a copy of it when passing it to the function or inside of the function itself.
Sending a copy to the function:
def mutation(L):
L.append("x")
L = []
mutation(list(L))
print(L)
# Prints out []
Making a copy inside the function:
def mutation(L):
L = list(L)
L.append("x")
L = []
mutation(L)
print(L)
# Prints out []
This is because you are actually changing the original list/object passed into the function. Note that the underlying principle is pass-by-object-reference. So changes to the passed parameter within the function will reflect outside.
If you don't want to change globally, use [:] to create a copy and this would perform operations on the copy without mutating the original list.
def mutation(L):
L = L[:]
L.append("x")
L = []
mutation(L)
print(L)
# []
If we create an empty list then we can fill this list by either appending "something"
list_ex1 = []
list_ex1.append(1)
print(list_ex1)
[1]
or we can reassign an empty list as the same "something".
list_ex2 = []
list_ex2 = [1]
print(list_ex2)
[1]
Great, we get the same result. However, there must be some very different happenings going on in the background. This became obvious when I was using tkinter to create a simple UI with some buttons on it.
def Multi_Import_Match(imp):
imp_fill = []
win = Tk()
win.title('Select Name')
win.geometry("500x100")
b = []
def but_call(imp):
imp_fill.append(imp) # Here is where the problem became apparent!!
win.destroy()
for i in range(0,len(imp)):
b.append(Button(win, text=imp[i], command=lambda i=i: but_call(imp[i])))
b[i].pack()
mainloop()
return imp_fill
I struggled for a while to get the expected output from my UI when using imp_fill = imp but it only returned an empty list. With imp_fill.append(imp) the code worked perfectly and returned my desired string. Why is it that the append works and the reassignment does not?
NB: the variable imp was a small list of strings.
The issue here is scope. Python will search outer levels of scope if it can't find a local definition for a name. That's why this can work:
val = "hello"
def print_val():
print(val)
print_val()
# hello
This becomes more confusing with lists though, as they're mutable. That means that if you append to a list from within a function, you are affecting it in the original scope. See this:
val = []
def print_val():
val.append("hello")
print(val)
print(val)
# []
print_val()
# ['hello']
print(val)
# ['hello']
The list is originally empty, but after calling print_val the list is appended to. This affects the actual list, which in turn means that when you just print it normally it has 'hello' in it.
In your case, if you just did the equivalent of val = ['hello'] inside the function, that only affects the value of val within the function, and nothing happens to the original scoped name. The solution is to either use return [val] to get the value from the function's scope or to use append as you did, which modifies the actual value that exists outside the function.
The difference comes from the fact that a list is a mutable object. So other references to same object are changed accordingly when you modify the object, but are left alone when you affect the reference to a different object
Let's use your example with an alternate reference:
>>> list_ex1 = []
>>> old = list_ex1
>>> list_ex1.append(1)
>>> print(list_ex1)
[1]
>>> old
[1]
>>> old is list_ex1
True
>>> list_ex2 = []
>>> old2 = list_ex2
>>> list_ex2 = [1]
>>> print(list_ex2)
[1]
>>> old2
[]
>>> old2 is list_ex2
False
In your example modifying the list with append also modifies the original object, where as affecting it only changes a local copy and leave the original object untouched.
First off, from a reading of this code, you should really change the argument name for the but_call() function to avoid confusion. The argument seems to be an element of the list imp, not your variable imp itself. Clarity is the key here.
Secondly, you are right that the statements are different:
imp_fill = imp replaces the pre-existing value of fill_imp ([] at start but changes every time this statement is executed) with that of imp (whatever it may be - list, str, int, object, ...)
imp_fill = [imp] replaces the pre-existing value of fill_imp with a list which has one element - the value of imp
imp_fill.append(imp) takes the pre-existing list and expands it by 1 element, which is the value of imp
So:
>>> a = [2]
>>> a = [9, 5]
>>> print(a)
[9, 5]
>>> a = [2]
>>> a = 943
>>> print(a)
943
>>> a = [2]
>>> a.append(98)
>>> print(a)
[2, 98]
I am trying to add an object to a list but since I'm adding the actual object when I try to reset the list thereafter, all the values in the list are reset.
Is there an actual way how I can add a monitor object to the list and change the values and not affect the ones I've already saved in the list?
Thanks
Code:
arrayList = []
for x in allValues:
result = model(x)
arrayList.append(wM)
wM.reset()
where wM is a monitor class - which is being calculated / worked out in the model method
Is your problem similar to this:
l = [[0]] * 4
l[0][0] += 1
print l # prints "[[1], [1], [1], [1]]"
If so, you simply need to copy the objects when you store them:
import copy
l = [copy.copy(x) for x in [[0]] * 4]
l[0][0] += 1
print l # prints "[[1], [0], [0], [0]]"
The objects in question should implement a __copy__ method to copy objects. See the documentation for copy. You may also be interested in copy.deepcopy, which is there as well.
EDIT: Here's the problem:
arrayList = []
for x in allValues:
result = model(x)
arrayList.append(wM) # appends the wM object to the list
wM.reset() # clears the wM object
You need to append a copy:
import copy
arrayList = []
for x in allValues:
result = model(x)
arrayList.append(copy.copy(wM)) # appends a copy to the list
wM.reset() # clears the wM object
But I'm still confused as to where wM is coming from. Won't you just be copying the same wM object over and over, except clearing it after the first time so all the rest will be empty? Or does model() modify the wM (which sounds like a terrible design flaw to me)? And why are you throwing away result?
You need to create a copy of the list before you modify its contents. A quick shortcut to duplicate a list is this:
mylist[:]
Example:
>>> first = [1,2,3]
>>> second = first[:]
>>> second.append(4)
>>> first
[1, 2, 3]
>>> second
[1, 2, 3, 4]
And to show the default behavior that would modify the orignal list (since a name in Python is just a reference to the underlying object):
>>> first = [1,2,3]
>>> second = first
>>> second.append(4)
>>> first
[1, 2, 3, 4]
>>> second
[1, 2, 3, 4]
Note that this only works for lists. If you need to duplicate the contents of a dictionary, you must use copy.deepcopy() as suggested by others.
while you should show how your code looks like that gives the problem, i think this scenario is very common. See copy/deepcopy
If i am correct in believing that you are adding a variable to the array but when you change that variable outside of the array, it also changes inside the array but you don't want it to then it is a really simple solution.
When you are saving the variable to the array you should turn it into a string by simply putting str(variablename). For example:
array.append(str(variablename))
Using this method your code should look like this:
arrayList = []
for x in allValues:
result = model(x)
arrayList.append(str(wM)) #this is the only line that is changed.
wM.reset()