Coming from much less dynamic C++, I have some trouble understanding the behaviour of this Python (2.7) code.
Note: I am aware that this is bad programming style / evil, but I would like to understand it non the less.
vals = [1,2,3]
def f():
vals[0] = 5
print 'inside', vals
print 'outside', vals
f()
print 'outside', vals
This code runs without error, and f
manipulates the (seemingly) global list. This is contrary to my prior understanding that global variables that are to be manipulated (and not only read) in a function must be declared as global ...
.
On the other hand, if I replace vals[0] = 5
with vals += [5,6]
, execution fails with an UnboundLocalError
unless I add a global vals
to f
. This is what I would have expected to happen in the first case as well.
Could you explain this behaviour?
Why can I manipulate vals
in the first case? Why does the second type of manipulation fail while the first does not?
Update:
It was remarked in a comment that vals.extend(...)
works without global
. This adds to my confusion - why is +=
treated differently from a call to extend
?
global
is needed when you want to assign to a variable in the outer scope. If you don't useglobal
, Python will considervals
as a local variable when doing assignments.+=
is an assignment (an augmented assignment) andvals += [5, 6]
is equivalent to readingvals
, then append[5, 6]
to that value and assign the resulting list back to the originalvals
. Becausevals += [5,6]
has noglobal
statement, Python sees the assignment and treatsvals
as local. You didn't create a local variable calledvals
but you try to append to it and from here theUnboundLocalError
.But for reading it is not necessary to use
global
. The variable will be looked up locally first then, if it's not found in the local scope, it's looked up in the outer scope and so on. And since you are dealing with a reference type you get back a reference when you do the read. You can change the content of the object trough that reference.That's why
.extend()
works (because it's called on the reference and acts on the object itself) whilevals += [5, 6]
fails (becausevals
is neither local nor markedglobal
).Here is a modified example to try out (using a local
vals
clears theUnboundLocalError
):global
is only needed when you are trying to change the object which the variable references. Becausevals[0] = 5
changes the actual object rather than the reference, no error is raised. However, withvals += [5, 6]
, the interpreter tries to find a local variable because it can't change the global variable.The confusing thing is that using the
+=
operator with list modifies the original list, likevals[0] = 5
. And whereasvals += [5, 6]
fails,vals.extend([5, 6])
works. We can enlist the help ofdis.dis
to lend us some clues.We can see that functions
a
andc
useLOAD_GLOBAL
, whereasb
tries to useLOAD_FAST
. We can see now why using+=
won't work - the interpreter tries to loadv
as a local variable because of it's default behaviour with in-place addition. Because it can't know whetherv
is a list or not, it essentially assumes that the line means the same asv = v + [1]
.As long as you do not change object reference, Python will preserve global object. Compare
The moment you try assignment, Python regards vals as local variable - and (oops) it's not there!