Function changes list values and not variable values in Python [duplicate]
Let's take a simple code:
y = [1,2,3]
def plusOne(y):
for x in range(len(y)):
y[x] += 1
return y
print plusOne(y), y
a = 2
def plusOne2(a):
a += 1
return a
print plusOne2(a), a
Values of 'y' change but value 'a' stays the same. I have already learned that it's because one is mutable and the other is not. But how to change the code so that the function doesn't change the list?
For example to do something like that (in pseudocode for simplicity):
a = [1,2,3,...,n]
function doSomething(x):
do stuff with x
return x
b = doSomething(a)
if someOperation(a) > someOperation(b):
do stuff
EDIT: Sorry, but I have another question on nested lists. See this code:
def change(y):
yN = y[:]
for i in range(len(yN)):
if yN[i][0] == 1:
yN[i][0] = 0
else:
yN[i][0] = 1
return yN
data1 = [[1],[1],[0],[0]]
data2 = change(data1)
Here it doesn't work. Why? Again: how to avoid this problem? I understand why it is not working: yN = y[:] copies values of y to yN, but the values are also lists, so the operation would have to be doubled for every list in list. How to do this operation with nested lists?
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.
Create a copy of the list. Using testList = inputList[:]
. See the code
>>> def plusOne(y):
newY = y[:]
for x in range(len(newY)):
newY[x] += 1
return newY
>>> y = [1, 2, 3]
>>> print plusOne(y), y
[2, 3, 4] [1, 2, 3]
Or, you can create a new list in the function
>>> def plusOne(y):
newList = []
for elem in y:
newList.append(elem+1)
return newList
You can also use a comprehension as others have pointed out.
>>> def plusOne(y):
return [elem+1 for elem in y]
You can pass a copy of your list, using slice notation:
print plusOne(y[:]), y
Or the better way would be to create the copy of list in the function itself, so that the caller don't have to worry about the possible modification:
def plusOne(y):
y_copy = y[:]
and work on y_copy
instead.
Or as pointed out by @abarnet in comments, you can modify the function to use list comprehension
, which will create a new list altogether:
return [x + 1 for x in y]
Just create a new list with the values you want in it and return that instead.
def plus_one(sequence):
return [el + 1 for el in sequence]
As others have pointed out, you should use newlist = original[:]
or newlist = list(original)
to copy the list if you do not want to modify the original.
def plusOne(y):
y2 = list(y) # copy the list over, y2 = y[:] also works
for i, _ in enumerate(y2):
y2[i] += 1
return y2
However, you can acheive your desired output with a list comprehension
def plusOne(y):
return [i+1 for i in y]
This will iterate over the values in y
and create a new list by adding one to each of them