The following code raises an UnboundLocalError
:
def foo():
i = 0
def incr():
i += 1
incr()
print(i)
foo()
Is there a way to accomplish this?
The following code raises an UnboundLocalError
:
def foo():
i = 0
def incr():
i += 1
incr()
print(i)
foo()
Is there a way to accomplish this?
Use nonlocal
statement
def foo():
i = 0
def incr():
nonlocal i
i += 1
incr()
print(i)
foo()
For more information on this new statement added in python 3.x, go to https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement
You can use i
as an argument like this:
def foo():
i = 0
def incr(i):
return i + 1
i = incr(i)
print(i)
foo()
See 9.2. Python Scopes and Namespaces:
if no
global
statement is in effect – assignments to names always go into the innermost scope.
Also:
The
global
statement can be used to indicate that particular variables live in the global scope and should be rebound there; thenonlocal
statement indicates that particular variables live in an enclosing scope and should be rebound there.
You have many solutions:
i
as an argument ✓ (I would go with this one)nonlocal
keywordNote that in Python2.x you can access non-local variables but you can't change them.
People have answered your question but no one seems to address why exactly this is happening.
The following code raises an
UnboundLocalError
So, why? Let's take a quote from the FAQ:
When you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope.
Inside your nested function you are performing an assignment with the +=
operator. What this means is that i += 1
will approximately perform i = i + 1
(from a binding perspective). As a result i
in the expression i + 1
will be searched for in the local scope (because it is used in the assignment statement) for function incr
where it will not be found, resulting in an UnboundLocalError: reference before assignment
.
There's many ways you can tackle this and python 3 is more elegant in the approach you can take than python 2.
The nonlocal
statements tells Python to look for a name in the enclosing scope (so in this case, in the scope of function foo()
) for name references:
def foo():
i = 0
def incr():
nonlocal i
i +=1
incr()
print(i)
Function attributes Python 2.x
and 3.x
:
Remember that functions are first class objects, as such they can store state. Use function attributes to access and mutate function state, the good thing with this approach is it works on all pythons and doesn't require global, nonlocal
statements.
def foo():
foo.i = 0
def incr():
foo.i +=1
incr()
print(foo.i)
The global
Statement (Python 2.x
, 3.x
):
Really the ugliest of the bunch, but gets the job done:
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
The option of passing an argument to the function gets the same result but it doesn't relate to mutating the enclosing scope in the sense nonlocal
and global
are and is more like the function attributes presented. It is creating a new local variable in function inc
and then re-binding the name to i
with the i = incr(i)
but, it does indeed get the job done.
In Python, int are immutable. So you could put your int in a mutable object.
def foo():
i = 0
obj = [i]
def incr(obj):
obj[0]+=1
incr(obj)
print(obj[0])
foo()
You can make i
global and use it.
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
but the most preferred way is to pass i
as a param to incr
def foo():
i = 0
def incr(arg):
arg += 1
return arg
i = incr(i)
print(i)
foo()
you could also use a lambda function:
def foo():
i=0
incr = lambda x: x+1
print incr(i)
foo()
I think the code is cleaner this way
Simple function attributes will not work in this case.
>>> def foo():
... foo.i = 0
... def incr():
... foo.i +=1
... incr()
... print(foo.i)
...
>>>
>>>
>>> foo()
1
>>> foo()
1
>>> foo()
1
You re assign your foo.i to 0
for every call of foo
.
It better to use hassattr
. But code become more complicated.
>>> def foo():
... if not hasattr(foo, 'i'):
... foo.i = 0
... def incr():
... foo.i += 1
... return foo.i
... i = incr()
... print(i)
...
>>>
>>>
>>> foo()
1
>>> foo()
2
>>> foo()
3
Also you can try this idea:
>>> def foo():
... def incr():
... incr.i += 1
... return incr.i
... incr.i = 0
... return incr
...
>>> a = foo()
>>> a()
1
>>> a()
2
>>> a()
3
>>> a()
4
May be it is more handy to wrap you incr into decorator.