Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

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?

share|improve this question
 
In general, the easiest way to avoid confusion caused by mutation is to avoid mutation. When that isn't appropriate, the answer is to make a copy and mutate that. There really aren't any other options. –  abarnert Jul 16 at 20:52
 
Since you accepted @kindall's answer, perhaps you will find this blog post helpful. It explains things in greater detail and (in my opinion) greater clarity. –  John Y Jul 18 at 16:45
add comment

6 Answers

up vote 3 down vote accepted

Python variables are 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 to a different value. 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 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.

share|improve this answer
 
This is a little confusing for novices (because nobody learning programming through Python has any idea what a pointer is), and misleading for anyone else (because variables aren't pointers, they're names, and the issue is about binding vs. mutation, not pointers variables vs. normal variables). –  abarnert Jul 16 at 20:51
 
I've made some changes to clarify some of that. I hope. –  kindall Jul 16 at 21:37
 
Thanks for explanation! –  TimoW Jul 17 at 8:22
add comment

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]
share|improve this answer
 
Wow, thanks for a rapid responses!! I tried newY = y and it didn't work, while should have been simple newY = y[:]. –  TimoW Jul 16 at 20:52
 
newY = y just creates another reference to it, while newY = y[:] creates a new list. –  Sukrit Kalra Jul 16 at 20:55
 
Yes, now I understand and everything becomes clear! Thanks! –  TimoW Jul 17 at 5:16
 
You're welcome. Feel free to accept my answer if it helped you out. :) –  Sukrit Kalra Jul 17 at 5:18
add comment

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]
share|improve this answer
2  
Or, even better, rewrite the code so it builds the copy on the fly: y = [x+1 for x in y]. –  abarnert Jul 16 at 20:49
 
@abarnert. Right :) thanks, edited the answer. :) –  Rohit Jain Jul 16 at 20:51
add comment

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]
share|improve this answer
add comment

To answer your edited question:

Copying nested data structures is called deep copying. To do this in Python, use deepcopy() within the copy module.

share|improve this answer
add comment

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

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.