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
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.
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.