Home > Back-end >  Python variable scoping in nested function calls
Python variable scoping in nested function calls

Time:11-26

I come to this code in a LeetCode problem

class Solution:
    def maxProduct(self, root):
        self.res = total = 0

        def s(root):
            if not root: return 0
            left, right = s(root.left), s(root.right)
            self.res = max(self.res, left * (total - left), right * (total - right))
            return left   right   root.val

        total = s(root)
        s(root)
        return self.res % (10**9   7)

I change self.res to res and the code breaks with UnboundLocalError: local variable 'res' referenced before assignment. Why res has to be initialized as self.res while total does not?

CodePudding user response:

The problem is that the nested function s has it's own variable scope. While it can read variables (like res) from the enclosing scope (maxProduct), any assignment (e.g. res = max(...)) will introduce a variable in the scope of s, instead of writing to the outer (res). Because in this case maxProduct wants to read the modified version of res that s produced, the trick of adding a member variable to self is used. (In that case, s only reads the reference to self, and then adds a member, avoiding the scope issue).

See this little example for demonstration:

def foo():
        def bar():
                x=2
        x=1
        bar()
        return x

foo() returns 1, not 2, because, the assignment to x in bar introduces a new variable in the scope of bar

CodePudding user response:

Why res has to be initialized as self.res while total does not?

It does not, but because of the rules of Python's implicit declarations, by default assignment is declaration.

Since s only reads from total, it necessarily has to come from somewhere else, but because of

 res = max(self.res, left * (total - left), right * (total - right))

Python will create a res variable which is local to s, so you really have two different variables with the same name, one in maxProduct and one in s. This is called shadowing.

You can tell Python that a variable being assigned to is not a local by explicitly declaring it so:

nonlocal res

This will make Python assign to the lexically closest variable of that name:

def foo():
    a = 1
    def bar():
        a = 2
    bar()
    print(a)

foo()
# 1

def foo():
    a = 1
    def bar():
        nonlocal a
        a = 2
    bar()
    print(a)

foo()
# 2
  • Related