I was writing a decorator that is initialized with an argument, and I tried setting that argument to None
by default and then check if that argument is None
and if so do argument = some_default_value
. I've boiled down the issue I ran into:
This code works (it prints None
):
def outer(arg=None):
def inner():
print(arg)
inner()
outer()
and this code works (it prints 22
):
def outer(arg=None):
def inner():
arg = 22
print(arg)
inner()
outer()
This code doesn't:
def outer(arg=None):
def inner():
if arg is None:
arg = 22
print(arg)
inner()
outer()
I get the following error:
Traceback (most recent call last):
File "test.py", line 8, in <module>
outer()
File "test.py", line 6, in outer
inner()
File "test.py", line 3, in inner
if arg is None:
UnboundLocalError: local variable 'arg' referenced before assignment
Why does using the variable arg
in an if
statement make Python decide that it must be defined in that scope and not in an outer scope?
The scope of a variable is determined when the function is defined. By assigning to arg
, you make arg
a local variable throughout the scope local to inner
, which means if arg is None
is now testing the as-yet undefined local variable arg
, not the non-local variable defined by outer
. (Put another way, the presence of an assignment statement in the body of the function makes the name local, not the actual execution of the statement, which creates the variable by that name.)
You could fix that by declaring arg
as non-local in the body of inner
:
def outer(arg=None):
def inner():
nonlocal arg
if arg is None:
arg = 22
print(arg)
inner()
outer()
though be aware that the change to arg
is visible after the call to inner
as well, because it is outer
's local variable arg
that you changed.