2

I have the code:

def main(m):
    res = m
    def help(a, b):
        print(res)
        #res = min(res, a*b)
    help(3,2)
    return res
main(3)

The code works. However

def main(m):
    res = m
    def help(a, b):
        print(res)
        res = min(res, a*b)
    help(3,2)
    return res
main(3)

it raise UnboundLocalError: local variable 'res' referenced before assignment

def main(m):
    global res
    res = m
    def help(a, b):
        print(res)
        res = min(res, a*b)
    help(3,2)
    return res
main(3)

It seems that I add global res does not change. what happens here? How to update res inside the function help?

jason
  • 1,998
  • 3
  • 22
  • 42
  • 2
    The issue is that adding the line `res = min(res, a*b)` actually introduces a new variable `res` within the `help` function which is *different* from the variable `res` within `main`. I'll write up a full answer in a sec. – Quelklef Jun 29 '20 at 17:15
  • 1
    Does this answer your question? [Short description of the scoping rules?](https://stackoverflow.com/questions/291978/short-description-of-the-scoping-rules) – MisterMiyagi Jun 29 '20 at 17:27

3 Answers3

2

You need to tell the inner function that the res variable is nonlocal, like this:

def main(m):
    res = m
    def help(a, b):
        nonlocal res
        print(res)
        res = min(res, a*b)
    help(3,2)
    return res
main(3)

You can read from a non locally scoped variable without issue in a function, but if you try to write to a variable then python assumes that you want to create a new locally scoped variable. That local variable hides and prevents access to any similarly named variable that may exist in the namespace of higher scopes, like outer functions or global variables. To inform it that you do not want to create a local variable and want to access the outer scope variable you need to use the nonlocal keyword as in the above example.

Using global res does not fix it because global variables are at the top level of the namespace hierarchy and the res variable that you are trying to access is not. The res you want to access is in between the global scope and the local scope. That is why you need to use nonlocal instead of global. If res were at the top/external level then global would have been the solution.

Glenn Mackintosh
  • 2,765
  • 1
  • 10
  • 18
  • cool, any idea why I add `global res` does not work? – jason Jun 29 '20 at 17:33
  • @jason Because global variables are at the top level of the namespace hierarchy and the res you are trying to access is not. The res you are accessing is in between the global scope and the local scope. That is why you need to use nonlocal instead of global. I have edited my answer to include this explanation which I left out in the first place. – Glenn Mackintosh Jun 29 '20 at 17:42
1

The problem is that you are introducing a new, uninitialized local variable in the help() function, then trying to pass it to the min(res, a*b) function.

Instead of trying to deal with the scoping issues, you could just return min(res, a*b) and assign it to res outside of the help() function:

def main(m):
    res = m
    def help(a, b):
        # print(res)
        return min(res, a*b)
    res = help(3,2)
    return res
adamgy
  • 4,543
  • 3
  • 16
  • 31
1

The relevant code here is

res = m
def help(a, b):
    print(res)
    res = min(res, a*b)  # may or may not be commented out

This is not doing what you think it is.

Let's go through this line-by-line:

  1. res = m: this defines the variable res, with value m, which lives in the main function
  2. def help(a, b): begins a function definition. Within the help function, you are able to define entirely new variables which are insulated from the variables in main
  3. print(res): this prints the value of variable res. To find the value of res, Python will first look in help function a variable named res. If there is none, it will search the main function (i.e. the parent scope) for a variable named res. (If there were to be none there, it would continue to search higher and higher scopes.) As it turns out, in your example, whether or not there is a variable named res in the help function depends on whether or not line 4 is commented out!
  4. res = min(res, a*b): This does not do what you think it does. What you want it to do is to change the variable value of the res variable in the main function. However, Python will instead create a new variable res within the help function and set its value to min(res, a*b).

This is why you are getting an UnboundLocalError. Because when line 4 is uncommented, then print(res) refers to the variable named res in the help function, not the main function. But when line 4 is commented out, there is no res variable in the help function, so print(res) instead uses the variable in the main function.

So, how to fix it? Actually pretty simple, just change the code to:

def help(a, b):
    nonlocal res
    print(res)
    res = min(res, a*b)

The nonlocal statement tells Python, "hey, the res variable is coming from an outer scope".

Quelklef
  • 1,999
  • 2
  • 22
  • 37
  • 1
    excellent explains. May I add `global res` before the `help()` function to turn `res` a global variable? – jason Jun 29 '20 at 17:37
  • Yes, that would work. However, by the looks of your code, it would be more appropriate to have no `global` statement anywhere and a single `nonlocal res` statement within the `help` function. (Since you `return res` from `main`, should `res` really be a global variable?) – Quelklef Jun 29 '20 at 17:39
  • I try add `global res` outside the `help()`, it still raise the error. – jason Jun 29 '20 at 17:41
  • 1
    I misread your question. Adding `global res` *outside* `help` will not work, because then you're referring to the `res` in the `main` function. I've offered a solution in my anwer, did you try that? – Quelklef Jun 29 '20 at 17:42