0

If I use a list as a parameter for a function, I would expect that the original list would be unmodified. Why is it when I use the code below, x == z is True when it should be x == range(10) is True?

In [83]: def pop_three(collection):
   ....:     new_collection = []
   ....:     new_collection.append(collection.pop())
   ....:     new_collection.append(collection.pop())
   ....:     new_collection.append(collection.pop())
   ....:     return new_collection, collection
   ....:

In [84]: x = range(10)

In [85]: x
Out[85]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [86]: y, z = pop_three(x)

In [87]: y
Out[87]: [9, 8, 7]

In [88]: z
Out[88]: [0, 1, 2, 3, 4, 5, 6]

In [89]: x
Out[89]: [0, 1, 2, 3, 4, 5, 6]
profesor_tortuga
  • 1,826
  • 2
  • 17
  • 27
  • See the page, http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference – han058 Sep 25 '14 at 00:23

4 Answers4

3

If I use a list as a parameter for a function, I would expect that the original list would be unmodified.

No. I think what you're missing here is that Python never automatically copies anything.

If you're used to a language like, say, C++, Python is very different. Ned Batchelder, as always, explains this brilliantly, but let me summarize:

  • In C++, a function parameter, or a variable, or an array element, is a location in memory, with a type. When you write a = b, or call f(b), that copies the value in memory location b to memory location a (possibly calling a casting operator or conversion constructor or copy constructor or copy assignment operator—or moving versions of half of those… but we're not here to learn about the intricacies of C++).
  • In Python, a function parameter, or a variable, or a list element, is just a name. The value has its own typed memory location, somewhere else. You can have as many names as you want for the same object. When you write a = b or call f(b), that just makes another name for the same object.

In particular, in your case, collection and x are names for the same object. If you added print(id(collection)) before the call and print(id(x)) inside the function, they would both print out the same number.


So, in Python, if you want a copy, you have to ask for it explicitly—e.g., pop_three(x[:]) or pop_three(copy.copy(x)).

Or, of course, you could do that inside pop_three.

Or, best of all, you could just avoid using mutating methods like pop in the first place:

def pop_three(collection):
    return collection[-3:][::-1], collection[:-3]

I was confused since strings or integers passed into a function can be treated in this fashion.

Well, they can be treated in this fashion exactly as far as lists can. If you never mutate anything, the distinction between separate copies and shared references is irrelevant.* Because Python strings and integers are immutable, the distinction can't come up in any code with strings or integers are arguments. Lists, on the other hand, are mutable, so the distinction can arise—but it's perfectly possible, and in fact often best, to write code that uses them without mutating anything, as in the final example above.

Notice that this assumes the first point above: that, unlike C++ assignment, Python assignment is not a form of mutation. But it can get tricky at the edges. For example, is x[0] = 1 a mutation? Well, it's not a mutation of x[0], but it is a mutation of x. What about x += y? The language leaves that up to the type of x, and for mutable types it usually (but not always) is a mutation, while for immutable types of course it isn't.

* This isn't quite true; you can write code that relies on identity even though it isn't relevant, e.g., by using the id function or the is operator. Such code is almost never Pythonic, or a good idea… but it can be helpful to learning how things work. Try to print(id(x)) suggestion above with x = "a" + "b". However, this can be misleading, because the Python interpreter is allowed to "intern" objects that it knows are guaranteed to be immutable, and it does so with, e.g., small integers, and the Python compiler is allowed to fold constants, and it does so with, e.g., literal strings.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I was confused since strings or integers passed into a function *can* be treated in this fashion. Thanks. – profesor_tortuga Sep 25 '14 at 04:08
  • @profesor_tortuga: No, they can't. Strings and integers are immutable. You – Matthias Sep 25 '14 at 08:08
  • @profesor_tortuga: I've updated the answer to try to explain that part better, because I realize as written it assumed that you'd figure it out on your own—which you clearly were able to do, but that might not be true for future askers of the same question. – abarnert Sep 25 '14 at 18:33
1

Because when you pass x to the pop_tree() and you called collection.pop(). This effectively did x.pop() Therefore when you turned from the funciton, x only got 7 elements

Steven
  • 475
  • 4
  • 14
0

z = pop_three(x) means you are passing in the list x and then mutating that list x using collection.pop() x so x is being mutated therefore could not be == range(10)

y becomes new_collection and z is collection which is the mutated returned value of x.

In [2]: x = range(10)

In [3]: y, z = pop_three(x)

In [4]: z is x # z is the object x
Out[4]: True
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
0

Lists are mutable. When you pop from a list, you modify the list.

Any variable that originally pointed to the list will continue to point to the same modified list. Any new variable that you point at the list will point to the same modified list as well.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331