While building a parameterized decorator, I had not realized reassignments to passed in arguments within nested functions were not allowed in Python. Looking further, I realized this is also true for simple functions as well. I have reduced the demonstration to the following nested function:
def a(s):
def b():
def c():
# nonlocal s # fix
print(s)
# while s:
# s -= 1 # uncommenting raises UnboundLocalError
print(s)
return None
return c()
return b()
a(3)
# 3
# 3
I would like the following desired output by adding the commented while
loop:
a(3)
# 3
# 0
Next, uncommenting the two lines of the while
loop gives the following error, which suggests that reassigning a value to s
raises an error:
<ipython-input-37-7141eb599936> in c()
3 def c():
4 # nonlocal s # fix
----> 5 print(s)
6 while s:
7 s -= 1 # uncommenting raises UnboundLocalError
UnboundLocalError: local variable 's' referenced before assignment
Finally, uncommenting nonlocal
fixes this issue and gives the desired output as suggested by this post.
Although the problem is solved, I would like to understand the source of the issue. I noticed the traceback points to the first use of the parameterized argument s
(e.g. print(s)
), rather than pointing to the lines that actually cause the error (i.e. the while
loop/assignment).
I suspect that upon calling a function, Python first establishes assignments of the local scope. Assignments then take higher precedence over or override inherited variables from the outer scopes. Thus without an assignment to s
, the outer s
is used. By contrast, with an assignment, s
is redefined at the function call, and any reference before the initial assignment will raise an error. Is this correct, or can someone explain what Python is actually doing?