The Explanation
Whenever a value is assigned to a variable inside a function, python considers that variable a local variable of that function. (It doesn't even matter if the assignment is executed or not - as long as an assignment exists in a function, the variable being assigned to will be considered a local variable of that function.) Since the statement ctr += 1
includes an assignment to ctr
, python thinks that ctr
is local to the inner
function. Consequently, it never even tries to look at the value of the ctr
variable that's been defined in outer
. What python sees is essentially this:
def inner():
ctr = ctr + 1
And I think we can all agree that this code would cause an error, since ctr
is being accessed before it has been defined.
(See also the docs or this question for more details about how python decides the scope of a variable.)
The Solution (in python 3)
Python 3 has introduced the nonlocal
statement, which works much like the global
statement, but lets us access variables of the surrounding function (rather than global variables). Simply add nonlocal ctr
at the top of the inner
function and the problem will go away:
def outer():
ctr = 0
def inner():
nonlocal ctr
ctr += 1
inner()
The Workaround (in python 2)
Since the nonlocal
statement doesn't exist in python 2, we have to be crafty. There are two easy workarounds:
Removing all assignments to ctr
Since python only considers ctr
a local variable because there's an assignment to that variable, the problem will go away if we remove all assignments to the name ctr
. But how can we change the value of the variable without assigning to it? Easy: We wrap the variable in a mutable object, like a list. Then we can modify that list without ever assigning a value to the name ctr
:
def outer():
ctr = [0]
def inner():
ctr[0] += 1
inner()
Passing ctr
as an argument to inner
def outer():
ctr = 0
def inner(ctr):
ctr += 1
return ctr
ctr = inner(ctr)