Variable scope between parent and sub-functions

I have a routine that uses a sub-function. The parent defines a variable of list[list[int]] which is used by the sub-function. The code works fine.

I now want to modify that sub-function to exit on first match. To do this I have changed the name of the variable and type hint in the parent function. I’ve also added a check in the sub-function to return if that variable is set. In short, I don’t think I’m changing anything as far as the variable scope is concerned, just how I am using the variable.

The problem is I am now getting an error ‘UnboundLocalError: local variable ‘var’ referenced before assignment’.

Below is a side by side diff of the code… (I’ve stripped comments, etc) Note the use of variables valid_combinations on the left and first_combination on the right.

What am I misunderstanding about variable scope? Why is it now a local to the sub-function?

def all_combinations(candidates: list[int], target: int) -> list[list[int]]: |  def shortest_combination(candidates: list[int], target: int) -> list[int]:

    def recursive_depth_first(index: int, current_target: int):                     def recursive_depth_first(index: int, current_target: int):
                                                                             |          if first_combination:
                                                                             |              return

        if not current_target:                                                          if not current_target:
            valid_combinations.append(temp_combination[:])                   |              first_combination = temp_combination[:]
            return                                                                          return
        if index >= len(candidates) or current_target < candidates[index]:              if index >= len(candidates) or current_target < candidates[index]:
            return                                                                          return

        recursive_depth_first(index + 1, current_target)                                recursive_depth_first(index + 1, current_target)
        temp_combination.append(candidates[index])                                      temp_combination.append(candidates[index])
        recursive_depth_first(index, current_target - candidates[index])                recursive_depth_first(index, current_target - candidates[index])
        temp_combination.pop()                                                          temp_combination.pop()

    temp_combination: list[int] = []                                                temp_combination: list[int] = []
    valid_combinations: list[list[int]] = []                                 |      first_combination: list[int] = []

    recursive_depth_first(0, target)                                                recursive_depth_first(0, target)
    return valid_combinations                                                |      return first_combination

Would you mind sharing the code you have a question on as plain code? Diffs can’t be run in Python ;)

Thanks for moving the post. Didn’t realize I was in the wrong topic.

I would be happy to share both the working and modified code. What’s the proper way to do that here, as reply, a link to a git gist or paste-bin?

This is the second version:

def shortest_combination(candidates: list[int], target: int) -> list[int]:

    def recursive_depth_first(index: int, current_target: int):
        if first_combination:
            return

        if not current_target:
            first_combination = temp_combination[:]
            return
        if index >= len(candidates) or current_target < candidates[index]:
            return

        recursive_depth_first(index + 1, current_target)
        temp_combination.append(candidates[index])
        recursive_depth_first(index, current_target - candidates[index])
        temp_combination.pop()

    temp_combination: list[int] = []
    first_combination: list[int] = []

    recursive_depth_first(0, target)

If you call it (let’s say with shortest_combination(list(range(4)), 3)) you get this error message:

UnboundLocalError: cannot access local variable 'first_combination' where it is not associated with a value

When the parser sees this assignment: first_combination = temp_combination[:] it makes the variable first_combination a local variable within the nested function recursive_depth_first(). This applies to the whole function. So when the first if statement if first_combination: gets executed there is no local variable first_combination yet.

If you want to assign to a captured variable from an outer scope, make that variable nonlocal explicitly (see realpython.com or stackoverflow.com)

Thank you. I understand the links and using nonlocal does resolve the problem.

However, I’m still a bit confused as to why the original code, below, ran. Note the line valid_combinations.append(temp_combination[:]) in the sub-function were valid_combinations was defined in the parent.

i.e. Why can the sub-function access valid_combinations without being told it’s nonlocal but it requires in the second example?

def all_combinations(candidates: list[int], target: int) -> list[list[int]]:

    def recursive_depth_first(index: int, current_target: int):



        if not current_target:
            valid_combinations.append(temp_combination[:])
            return
        if index >= len(candidates) or current_target < candidates[index]:
            return

        recursive_depth_first(index + 1, current_target)
        temp_combination.append(candidates[index])
        recursive_depth_first(index, current_target - candidates[index])
        temp_combination.pop()

    temp_combination: list[int] = []
    valid_combinations: list[list[int]] = []

    recursive_depth_first(0, target)
    return valid_combinations

Why can the sub-function access valid_combinations without being told it’s nonlocal but it requires when in the second example?

In the first version the nested function did only access the variable. Since there’s no local variable with the name valid_combinations Python will capture the variable from the outer function.

In the second version there’s an assignment. That’s Python’s way of creating a variable. Python cannot tell that you want to modify a captured variable, it will make first_combination a local variable … unless you explicitly specify that it should be nonlocal.

Here, in a codeblock.

You only need nonlocal when assigning a value (var = something). In that case, nonlocal tells Python not to create a new local var; instead, it should update an existing var. If you’re not using an assignment (list.append(val)), then there’s no need for scoping; you’re not assigning so there’s no where for Python to create a variable in the local scope.

Got it. Thank you. Very much appreciated and I learned something new.

1 Like

Understood and thank you. Very much appreciated.