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?
sis available in frame ofa(), but it does not clearly indicate howsis observed in the nested functions.