2

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?

pylang
  • 40,867
  • 14
  • 129
  • 121
  • Perhaps you could take a look at how Python executes the file line-by-line using http://www.pythontutor.com! – Deem Apr 25 '17 at 03:44
  • @Windmill I appreciate the suggestion. At the moment, pythontutor shows that `s` is available in frame of `a()`, but it does not clearly indicate how `s` is observed in the nested functions. – pylang Apr 25 '17 at 16:00

1 Answers1

2

If a function contains an assignment to a variable (including augmented assignments such as -=, that variable is automatically local, unless explicitly declared as global (or nonlocal). If there are no assignments, it's automatically global, without needing any declaration (since it could hardly be a local variable when there's no source of a value for it). This analysis is performed before any code is generated, so you get situations like this where a subsequent line of code can cause an earlier line to become an error.

jasonharper
  • 9,450
  • 2
  • 18
  • 42