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?

2
  • Perhaps you could take a look at how Python executes the file line-by-line using pythontutor.com! Commented Apr 25, 2017 at 3: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. Commented Apr 25, 2017 at 16:00

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This confirms my suspicion. Are you aware of a reference that that supports this?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.