3

Possible Duplicate:
Understanding Python's call-by-object style of passing function arguments

I recently came across this:

x = [1,2,3]

def change_1(x):
    x = x.remove(x[0])
    return x

Which results in:

>>> change_1(x)
>>> x
[2, 3]

I find this behavior surprising, as I thought that whatever goes in inside a function has no effect on outside variables. Furthermore, I constructed an example where basically do the same thing but without using remove:

x = [1,2,3]

def change_2(x):
    x = x[1:]
    return x

Which results in:

>>> change_2(x)
[2, 3] # Also the output prints out here not sure why this is
>>> x
[1, 2, 3]

And I get the result that I would expect, the function does not change x.

So it must be something remove specific that has effect. What is going on here?

Community
  • 1
  • 1
Akavall
  • 82,592
  • 51
  • 207
  • 251
  • 3
    This has been covered before: see [Understanding Python's call-by-object style of passing function arguments](http://stackoverflow.com/questions/10262920/understanding-pythons-call-by-object-style-of-passing-function-arguments). Also see [the definitive treatment of the subject](http://effbot.org/zone/call-by-object.htm). – Daniel Pryden Apr 23 '12 at 02:34
  • 2
    [Python tutorial 4.6](http://docs.python.org/release/2.7/tutorial/controlflow.html#defining-functions), 6th paragraph (also look at the footnote). These things _are_ actually expained in tutorials. – jogojapan Apr 23 '12 at 02:35
  • 1
    What value do you think `x.remove(x[0])` returns? What does it *actually* return? What is the difference between mutating an object and rebinding and existing name to a newly created object? – Steven Rumbalski Apr 23 '12 at 02:42
  • Maybe, a good place for you to lookat some examples, and do some exercises to fully grasp how the language works is http://learnpythonthehardway.org/ – jsbueno Apr 23 '12 at 04:03
  • 2
    It is clear Akavall understand local variables and the semantics of passing a list to a function. (Why is everyone assuming that he doesn't!). He's basically answered the question for himself: his suspicion is right: it is something specific to `remove`. `remove` is a destructive operation (it mutates the object). Slice extraction is not destructive; it computes a new list which is like the original, but with parts removed. That's it. – Kaz Apr 23 '12 at 04:21

3 Answers3

7

One of the things that is confusing is that you've called lots of different things 'x'. For example:

def change_1(x):       # the x parameter is a reference to the global 'x' list
    x = x.remove(x[0]) # on the left, though, is a new 'x' that is local to the function
    return x           # the local x is returned

>>> x = [1, 2, 3]
>>> y = change_1(x)    # assign the return value to 'y'
>>> print y
None                   # this is None because x.remove() assigned None to the local 'x' inside the function
>>> print x
[2, 3]                 # but x.remove() modified the global x inside the function

def change_2(x):
    x = x[1:]          # again, x on left is local, it gets a copy of the slice, but the 'x' parameter is not changed
    return x           # return the slice (copy)

>>> x = [1, 2, 3] 
>>> y = change_2(x)
>>> print x
[1, 2, 3]             # the global 'x' is not changed!
>>> print y
[2, 3]                # but the slice created in the function is assigned to 'y'
alan
  • 4,752
  • 21
  • 30
2

You would get the same result if you called the parameter of your function n, or q.

It's not the variable name that's being affected. The x in the scope of your list and the x outside that scope are two different "labels". Since you passed the value that x was attached to, to change_1() however, they are both referring to the same object. When you do x.remove() on the object in the function, you are basically saying: "get the object that x refers to. Now remove an element from that object. This is very different than a lhs assignment. If you did y=0; x=y inside your function. Your not doing anything to the list object. You're basically just tearing the 'x' label of of [1, 2. 3] and putting it on whatever y is pointing to.

Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
1

Your variable x is just a reference to the list you've created. When you call that function, you are passing that reference by value. But in the function, you have a reference to the same list So when the function modifies it, it is modified at any scope.

Also, when executing commands in the interactive interpreter, python prints the return value, if it is not assigned to a variable.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • 4
    Terminology quibble: it's not passed by reference; it's a reference type passed by value. Passing by reference would imply that you could assign another list to x inside the function and that the change would be reflected in the caller's scope. –  Apr 23 '12 at 02:34
  • 1
    Edited to reflect your comment. Thank you for that! – Jonathon Reinhart Apr 23 '12 at 02:37
  • 1
    I'd argue that it's not passed by reference *or* passed by value -- Python uses what Liskov called "call by object sharing", which is a separate concept. The key point is that Python *variables*, while bearing a superficial similarity, are not at all like variables in C-style languages. – Daniel Pryden Apr 23 '12 at 02:37
  • I don't see how "call by object sharing" is different than passing a reference type by value (i.e. the value is a reference to object), but it's possible that I'm missing some subtlety. I'm also not sure how variables come into play here, since the variable `x` in the caller is a different variable than the `x` parameter in function. AFAIK, this all works the same as it does in, say, Ruby or C#. –  Apr 23 '12 at 02:48
  • 1
    @IsaacCambron: You're right, the difference is subtle at best. I mainly prefer the "call by object sharing" terminology because it's more succinct than "call by value where the value is a reference" and less likely to get confused with "call by reference", which is a different concept where the word "reference" doesn't even mean the same thing. It's true, Python's model is pretty much the same as Ruby's, but be careful with comparisons to C#, since C# has some added wrinkles due to the dichotomy of value types and reference types. – Daniel Pryden Apr 23 '12 at 03:00