3

I study programming languages and a quiz question and solution was this:

def foo(x):
   x.append (3)
   x = [8]
   return x

x=[1, 5]
y= foo(x)
print x
print y

Why does this print as follows:

[1 5 3 ]
[8]

Why doesn't x equal to 8 ??

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Irem Herguner
  • 47
  • 1
  • 8
  • 2
    @AdamSmith Except when you `print x`, because then there's a `3` appended. You are changing `x` inside the function (since it's a mutable list), but then you reassign `x` to something else. The old `x` still exist (and has changed), and the new `x` is returned (and assigned to `y`). –  Jan 07 '15 at 14:28
  • Please see at [how-do-i-pass-a-variable-by-reference][1] [1]: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference Maybe it can help you! – trantu Jan 07 '15 at 14:52

5 Answers5

6

The other two answers are great. I suggest you to try id to get address.

See the following example

def foo(x):
   x.append (3)
   print "global",id(x)
   x = [8]
   print "local ",id(x)
   return x

x=[1, 5]
print "global",id(x)
y= foo(x)
print "global",id(x)
print x
print y

And the output

global 140646798391920
global 140646798391920
local  140646798392928
global 140646798391920
[1, 5, 3]
[8]

As you can see, the address of the variable x remains same when you manipulate it but changes when you use =. Variable assignment inside a function makes the variable local to the function

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
5

You have a lot of things going on there. So let's go step by step.

x = [1,5] You are assigning a list of 1,5 to x

y=foo(x) You are calling foo and passing in x and assigning whatever gets returned from foo

inside foo you call x.append(3) which appends 3 to the list that was passed in.

you then set x = [8] which is now a reference to a local variable x which then gets returned from foo ultimately setting y = [8]

user2097159
  • 862
  • 5
  • 13
4

Object References

The key to understanding this is that Python passes variables around using object references. These are similar to pointers in a language like c++, but are different in very key ways.

When an assignment is made (using the assignment operator, =):

x = [1, 5]

Actually TWO things have been created. First is the object itself, which is the list [1, 5]. This object is a separate entity from the second thing, which is the (global) object reference to the object.

Object(s)       Object Reference(s)
[1, 5]          x (global)                    <--- New object created

In python, objects are passed into functions by object reference; they are not passed "by reference" or "by value" like in c++. This means that when x is passed into the foo function, there is a new, local object reference to the object created.

Object(s)       Object Reference(s)
[1, 5]          x (global), x (local to foo)  <--- Now two object references

Now inside of foo we call x.append(3), which directly changes the object (referred to by the foo-local x object reference) itself:

Object(s)       Object Reference(s)
[1, 5, 3]       x (global), x (local to foo)  <--- Still two object references

Next, we do something different. We assign the local-foo x object reference (or re-assign, since the object reference already existed previously) to a NEW LIST.

Object(s)       Object Reference(s)
[1, 5, 3]       x (global)                    <--- One object reference remains
[8]             x (local to foo)              <--- New object created

Notice that the global object reference, x, is still there! It has not been impacted. We only re-assigned the local-foo x object reference to a NEW list.

And finally, it should be clear that when the function returns, we have this:

Object(s)       Object Reference(s)
[1, 5, 3]       x (global)                    <--- Unchanged
[8]             y (global), x (local to foo)  <--- New object reference created

Sidebar

Notice that the local-foo x object reference is still there! This is important behavior to understand, because if we do something like this:

def f(a = []):
    a.append(1)
    return a
f()
f()
f()

We will NOT get:

[1]
[1]
[1]

Instead, we will get:

[1]
[1, 1]
[1, 1, 1]

The statement a = [] is only evaluated ONCE by the interpreter when the program first runs, and that object reference never gets deleted (unless we delete the function itself).

As a result, when f() is called, local-f a is not changed back to []. It "remembers" its previous value; this is because that local object reference is still valid (i.e., it has not been deleted), and therefore the object does not get garbage collected between function calls.

Contrast with Pointers

One of the ways object references are different from pointers is in assignment. If Python used actual pointers, you would expect behavior such as the following:

a = 1
b = a
b = 2
assert a == 2

However, this assertion produces an error. The reason is that b = 2 does NOT impact the object "pointed to" by the object reference, b. It creates a NEW object (2) and re-assigns b to that object.

Another way object references are different from pointers is in deletion:

a = 1
b = a
del a
assert b is None

This assertion also produces an error. The reason is the same as in the example above; del a does NOT impact the object "pointed to" by the object reference, b. It simply deletes the object reference, a. The object reference, b, and the object it points to, 1, are not impacted.

You might be asking, "Well then how do I delete the actual object?" The answer is YOU CAN'T! You can only delete all the references to that object. Once there are no longer any references to an object, the object becomes eligible for garbage collection and it will be deleted for you (although you can force this to happen using the gc module). This feature is known as memory management, and it is one of the primary strengths of Python, and it is also one of the reasons why Python uses object references in the first place.

Mutability

Another subject that needs to be understood is that there are two types of objects: mutable, and immutable. Mutable objects can be changed, while immutable objects cannot be changed.

A list, such as [1, 5], is mutable. A tuple or int, on the other hand, is immutable.

The append Method

Now that all of this is clear, we should be able to intuit the answer to the question "How does append work in Python?" The result of the append() method is an operation on the mutable list object itself. The list is changed "in place", so to speak. There is not a new list created and then assigned to the foo-local x. This is in contrast to the assignment operator =, which creates a NEW object and assigns that object to an object reference.

Rick
  • 43,029
  • 15
  • 76
  • 119
2

The append function modifies the x that was passed into the function, whereas assigning something new to x changed the locally scoped value and returned it.

mbomb007
  • 3,788
  • 3
  • 39
  • 68
2

The scope of x inside foo is specific to the function and is independent from the main calling context. x inside foo starts out referencing the same object as x in the main context because that's the parameter that was passed in, but the moment you use the assignment operator to set it to [8] you have allocated a new object to which x inside foo points, which is now totally different from x in the main context. To illustrate further, try changing foo to this:

def foo(x):
   print("Inside foo(): id(x) = " + str(id(x)))
   x.append (3)
   print("After appending: id(x) = " + str(id(x)))
   x = [8]
   print("After setting x to [8], id(x) = " + str(id(x)))
   return x

When I executed, I got this output:

Inside foo(): id(x) = 4418625336
After appending: id(x) = 4418625336
After setting x to [8], id(x) = 4418719896
[1, 5, 3]
[8]

(the IDs you see will vary, but the point will still be clear I hope)

You can see that append just mutates the existing object - no need to allocate a new one. But once the = operator executes, a new object gets allocated (and eventually returned to the main context, where it is assigned to y).

rchang
  • 5,150
  • 1
  • 15
  • 25