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 ??
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 ??
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
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]
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
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.
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.
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.
append
MethodNow 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.
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.
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
).