This is because for your second example, the compiler tries to help you out with a bit of magic. In the case of
def f():
print(a)
Here's the bytecode that it reduces to:
dis.dis(f)
2 0 LOAD_GLOBAL 0 (print)
3 LOAD_GLOBAL 1 (a) #aha!
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
You can see that since you never assigned to a
within the scope of f
, the compiler knows you must be referring to a global.
When you start assigning to a
, now the compiler won't try to help you out. Unless you've explicitly told it that a
is a global, it will treat it as a local.
def g():
x += 1
dis.dis(g)
2 0 LOAD_FAST 0 (x) #note no assumption that it is global
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_FAST 0 (x)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
And now you can ask the question, okay, but why does the compiler not help you out in your first example? One way of explaining it is of course "explicit is better than implicit". Though of course the compiler is implicitly doing stuff in your second example, so maybe that's not a satisfying explanation :).
Mostly it comes down to This Is How Python Works, I'd say. If you assign to a variable, python will treat it as local to that scope unless you tell it otherwise. So your statement:
Global variables cannot be accessed within a function without using
the global keyword
is not quite correct. You can access variables from an outer scope but you cannot assign to said variables without explicitly declaring you want to.
Sidenote, this is perfectly legal:
x = [1]
def f():
x[0] += 1
f()
#x is now [2]
Which may be confusing :-). This is because in this context, you're not assigning anything over the reference that x
holds; the +=
operator actually invokes the __setattr__
method of lists in order to alter an attribute. In python 2 this trick is frequently used as a workaround for the fact that there is no nonlocal
keyword.