Question

using variable out of nested function

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        balanced = [True]
        
        def node_height(root):
            if not root or not balanced[0]:
                return 0
            
            left_height = node_height(root.left)
            right_height = node_height(root.right)
            
            if abs(left_height - right_height) > 1:
                balanced[0] = False
                return 0
            
            return 1 + max(left_height, right_height)
            
        node_height(root)
        return balanced[0]

I understand why the code above works but when I change the value of variable 'balanced' to balanced = True instead of balanced = [True] in line 3 and change balanced[0] to balanced in line 6, 13 and 19, I get an error.

class solution:
    def func(): 
        a = 5
        b = 7
        c = True
        def nested_func():
            return (a + b, c)
        return nested_func()
        
    print('sum is:', func())

So I tried the code above to test (maybe nested function cannot get variable that has boolean as a value) but was able to get the result of 'sum is: (12, True)' which shows that nested functions are able to get variables outside of them.

Could someone explain this?

 4  57  4
1 Jan 1970

Solution

 5

Nested functions can access variables from their parent functions. However, in your first case, not only you are accessing the value of the balanced variable, but you are also attempting to modify it.

Whenever you create a variable in a nested function with the same name as a variable in the parent function, the nested function uses its own variable that is created inside the nested function.

So in this code

    def node_height(root):
        if not root or not balanced:
            return 0
        
        left_height = node_height(root.left)
        right_height = node_height(root.right)
        
        if abs(left_height - right_height) > 1:
            balanced = False #  You are creating the variable here, 
                             #  but you are trying to access it above in the first if-else statement. 
                             # That is why you are getting the UnboundlocalError
            return 0
        
        return 1 + max(left_height, right_height)

And about the first list approach, as you know list are mutable so you can update them inside the function, and it will update in all scope, so that is why it was working.


And in this example, As already Answered by Aemyl and mentioned by me above ("Nested functions can access variables from their parent functions."), In this code you are not updating the value instead you are just accessing the value of the parent's variable(s), so that is why you are not getting any error.

class solution:
    def func(): 
        a = 5
        b = 7
        c = True
        def nested_func():
            return (a + b, c)
        return nested_func()
        
    print('sum is:', func())
2024-07-04
Sharim09

Solution

 3

You can use the nonlocal keyword for that:

...
        balanced = True
        def node_height(root):
            nonlocal balanced
...

When the definition of a function or class is nested (enclosed) within the definitions of other functions, its nonlocal scopes are the local scopes of the enclosing functions.

In your case, node_height is nested within isBalanced, so nonlocal balanced will tell the interpreter to use the same balanced reference as isBalanced, instead of creating a new reference that shadows the other one.

In your second example, nonlocal is not necessary because nested_func is only reading the variables. If you look at the following comparison:

>>> import dis
>>> dis.dis("def f(): return a")
<7 lines omitted>
Disassembly of <code object f at 0x0000026F07FDC510, file "<dis>", line 1>:
  1           0 RESUME                   0
              2 LOAD_GLOBAL              0 (a)
             12 RETURN_VALUE
>>> dis.dis("""
... def f():
...     a = 7
...     return a""")
<7 lines omitted>
Disassembly of <code object f at 0x0000026F07FDE4C0, file "<dis>", line 2>:
  2           0 RESUME                   0

  3           2 LOAD_CONST               1 (7)
              4 STORE_FAST               0 (a)

  4           6 LOAD_FAST                0 (a)
              8 RETURN_VALUE

You can see that the first function has the instruction LOAD_GLOBAL, whereas the second function uses LOAD_FAST, because it assigns a value to a, telling the interpreter that it is a local variable.

2024-07-04
Aemyl