Python: why does my list change when I'm not actually changing it?
That's because both list
and list2
are referring to the same list after you did the assignment list2=list
.
Try this to see if they are referring to the same objects or different:
id(list)
id(list2)
An example:
>>> list = [1, 2, 3, 4, 5]
>>> list2 = list
>>> id(list)
140496700844944
>>> id(list2)
140496700844944
>>> list.remove(3)
>>> list
[1, 2, 4, 5]
>>> list2
[1, 2, 4, 5]
If you really want to create a duplicate copy of list
such that list2
doesn't refer to the original list but a copy of the list, use the slice operator:
list2 = list[:]
An example:
>>> list
[1, 2, 4, 5]
>>> list2
[1, 2, 4, 5]
>>> list = [1, 2, 3, 4, 5]
>>> list2 = list[:]
>>> id(list)
140496701034792
>>> id(list2)
140496701034864
>>> list.remove(3)
>>> list
[1, 2, 4, 5]
>>> list2
[1, 2, 3, 4, 5]
Also, don't use list
as a variable name, because originally, list
refers to the type list, but by defining your own list
variable, you are hiding the original list
that refers to the type list. Example:
>>> list
<type 'list'>
>>> type(list)
<type 'type'>
>>> list = [1, 2, 3, 4, 5]
>>> list
[1, 2, 3, 4, 5]
>>> type(list)
<type 'list'>
In Python, why when I pass a list to function, I can not change it directly?
You would like to google "python pass by assignment" for example.
When you pass an argument to a function, the passed object is assigned to the internal variable of the function. For example, in
def myFun(x):
for i in range(len(x)):
x[i] += 2
lst = [10, 11, 12, 13, 14, 15]
myFun(lst)
you are first bounding a name lst
to an object [10, 11, ..., 15]
(outside the function). Then you pass this to the function, so that inside the function, x
refers to the same object which lst
was refering to. So you have two names (lst
outside the function and x
inside the function) for the same object. When the function does x[i] += 2
, it goes to this object (i.e., the list) and does the work. It does not care whether it is called x
or lst
; it just increment the i-th element of that specific object. Since x
and lst
are just different names of the object, after the function call, lst
refers to that (modified) object. x
is not available outside the function; it is just discarded. But you still have the name lst
, which still points to the (modified) object.
Now in
def myFun(x):
x = [20, 30, 40]
lst = [10, 11, 12, 13, 14, 15]
myFun(lst)
The procedure is the same; x
first refers to the object lst
was refering to: the object [10, ..., 15]
. But then you say "now x
, listen to me. Don't point to that object. You now refer to a newly created list [20, 30, 40]
." This is what the assignment x = ...
does. Now x
and lst
points to totally different objects. As above, x
is discarded, and so is the object it refered to. lst
still points to the object it has been pointing, which was not modified by the function.
Why does not the + operator change a list while .append() does?
So in a function the adding a 6 to the set doesn't show but it does when not in a function?
No, that is not what happens.
What happens is that, when you execute mylist = mylist + [6]
, you are effectively creating an entirely new list and putting it in the local mylist
variable. This mylist
variable will vanish after the execution of the function and the newly created list will vanish as well.
OTOH when you execute mylist.append(6)
you do not create a new list. You get the list already in the mylist
variable and add a new element to this same list. The result is that the list (which is pointed by list2
too) will be altered itself. The mylist
variable will vanish again, but in tis case you altered the original list.
Let us see if a more visual explanation can help you :)
What happens when you call proc()
When you write list1 = [1, 2, 3, 4, 5]
you are creating a new list object (at the right side of the equals sign) and creating a new variable, list1
, which will point to this object.
Then, when you call proc()
, you create another new variable, mylist
, and since you pass list1
as parameter, mylist
will point to the same object:
However, the operation mylist + [6]
creates a whole new list object whose contents are the contents of the object pointed by mylist
plus the content of the following list object - that is, [6]
. Since you attribute this new object to mylist
, our scenario changes a bit and mylist
does not point to the same object pointed by list1
anymore:
What I have not said is that mylist
is a local variable: it will disappear after the end of the proc()
function. So, when the proc()
execution ended, the mylist
is gone:
Since no other variable points to the object generated by mylist + [6]
, it will disappear, too (since the garbage collector* will collect it):
Note that, in the end, the object pointed by list1
is not changed.
What happens when you call proc2()
Everything changes when you call proc2()
. At first, it is the same thing: you create a list...
...and pass it as a parameter to a function, which will generate a local variable:
However, instead of using the +
concatenation operator, which generates a new list, you apply the append()
method to the existing list. The append()
method does not create a new object; instead, it _changes the existing one:
After the end of the function, the local variable will disappear, but the original object pointed by it and by list1
will be already altered:
Since it is still pointed by list1
, the original list is not destroyed.
EDIT: if you want to take a look at all this stuff happening before your eyes just go to this radically amazing simulator:
* If you do not know what is garbage collector... well, you will discover soon after understanding your own question.
List of lists changes reflected across sublists unexpectedly
When you write [x]*3
you get, essentially, the list [x, x, x]
. That is, a list with 3 references to the same x
. When you then modify this single x
it is visible via all three references to it:
x = [1] * 4
xs = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
f"id(xs[0]): {id(xs[0])}\n"
f"id(xs[1]): {id(xs[1])}\n"
f"id(xs[2]): {id(xs[2])}"
)
# id(xs[0]): 140560897920048
# id(xs[1]): 140560897920048
# id(xs[2]): 140560897920048
x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"xs: {xs}")
# xs: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]
To fix it, you need to make sure that you create a new list at each position. One way to do it is
[[1]*4 for _ in range(3)]
which will reevaluate [1]*4
each time instead of evaluating it once and making 3 references to 1 list.
You might wonder why *
can't make independent objects the way the list comprehension does. That's because the multiplication operator *
operates on objects, without seeing expressions. When you use *
to multiply [[1] * 4]
by 3, *
only sees the 1-element list [[1] * 4]
evaluates to, not the [[1] * 4
expression text. *
has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4]
, and no idea you even want copies, and in general, there might not even be a way to copy the element.
The only option *
has is to make new references to the existing sublist instead of trying to make new sublists. Anything else would be inconsistent or require major redesigning of fundamental language design decisions.
In contrast, a list comprehension reevaluates the element expression on every iteration. [[1] * 4 for n in range(3)]
reevaluates [1] * 4
every time for the same reason [x**2 for x in range(3)]
reevaluates x**2
every time. Every evaluation of [1] * 4
generates a new list, so the list comprehension does what you wanted.
Incidentally, [1] * 4
also doesn't copy the elements of [1]
, but that doesn't matter, since integers are immutable. You can't do something like 1.value = 2
and turn a 1 into a 2.
Function changes list values and not variable values in Python
Python variables contain pointers, or references, to objects. All values (even integers) are objects, and assignment changes the variable to point to a different object. It does not store a new value in the variable, it changes the variable to refer or point to a different object. For this reason many people say that Python doesn't have "variables," it has "names," and the =
operation doesn't "assign a value to a variable," but rather "binds a name to an object."
In plusOne
you are modifying (or "mutating") the contents of y
but never change what y
itself refers to. It stays pointing to the same list, the one you passed in to the function. The global variable y
and the local variable y
refer to the same list, so the changes are visible using either variable. Since you changed the contents of the object that was passed in, there is actually no reason to return y
(in fact, returning None
is what Python itself does for operations like this that modify a list "in place" -- values are returned by operations that create new objects rather than mutating existing ones).
In plusOne2
you are changing the local variable a
to refer to a different integer object, 3
. ("Binding the name a
to the object 3
.") The global variable a
is not changed by this and continues to point to 2
.
If you don't want to change a list passed in, make a copy of it and change that. Then your function should return the new list since it's one of those operations that creates a new object, and the new object will be lost if you don't return it. You can do this as the first line of the function: x = x[:]
for example (as others have pointed out). Or, if it might be useful to have the function called either way, you can have the caller pass in x[:]
if he wants a copy made.
Why am I getting a change in list elements but not the subtracted value of that element, when using for loop and print(my_list[i-1])
The list index in the expression my_list[i-1]
is the part between the brackets, i.e. i-1
. So by subtracting in there, you are indeed modifying the index. If instead you want to modify the value in the list, that is, what the index is pointing at, you would use my_list[i] - 1
. Now, the subtraction comes after the retrieval of the list value.
Changing one list unexpectedly changes another, too
Why does v change at all?
vec
and v
are both references.
When coding vec = v
you assign v
address to vec
.
Therefore changing data in v
will also "change" vec
.
If you want to have two different arrays use:
vec = list(v)
Related Topics
How to Install a Package Inside Virtualenv
How to Set Xlim and Ylim for a Subplot in Matplotlib
Text Box with Line Wrapping in Matplotlib
Python Pack() and Grid() Methods Together
How to Add Hours to Current Time in Python
Beautifulsoup:Difference Between .Find() and .Select()
How to Load/Edit/Run/Save Text Files (.Py) into an Ipython Notebook Cell
Reading File Opened with Python Paramiko Sftpclient.Open Method Is Slow
Is Tensorflow Compatible with a Windows Workflow
Python: Urllib2 How to Send Cookie with Urlopen Request
Iterate Over All Combinations of Values in Multiple Lists in Python
Using Django Database Layer Outside of Django