LeetCode #1120 Maximum Average Subtree (Python) This code snippet shows 2 common patterns I see a lot in Python that I don't really like:


Arbitrarily-named, narrowly-scoped recursive function
Inappropriate data type (in this case, an array ans to store 1 float) used purely for its mutability and scope, to store side-effects from some other scope


# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maximumAverageSubtree(self, root: TreeNode) -&gt; float:
        ans = [0]
        def helper(node):
            if not node:
                return 0, 0
            left_sum, left_count = helper(node.left)
            right_sum, right_count = helper(node.right)
            ans[0] = max(ans[0], (left_sum + right_sum + node.val) / (left_count + right_count + 1))
            return left_sum + right_sum + node.val, left_count + right_count + 1
        helper(root)
        return ans[0]


ans has to be an array, even though we just want to store one scalar value, because if we tried to use a float it would not be visible inside helper. This seems like an ugly shortcut to me.

And the other pattern I don't like is defining helper just so we can do recursion with additional return values for logic, then return helper's side effects. Is it normal to define short, sort of "throwaway" functions like this?
Non-Local Variables

ans is visible (for reading) inside of helper.  It just can't be changed, although its contents can be modified.

So yes,  ans = [0] is an ugly hack.  You don't need it.  What you need in its place is nonlocal.

def maximumAverageSubtree(self, root: TreeNode) -&gt; float:
    ans = 0

    def helper(node):
        nonlocal ans     # &lt;--- Refer to ans in outer (but not global) scope

        if not node:
            return 0, 0

        left_sum, left_count = helper(node.left)
        right_sum, right_count = helper(node.right)

        ans = max(ans, (left_sum + right_sum + node.val) / (left_count + right_count + 1))

        return left_sum + right_sum + node.val, left_count + right_count + 1

    helper(root)

    return ans


Arbitrary-named, narrowly-scoped recursive function

In this case, you're going to need to get used to it.

Instead of creating a new function, which is visible outside the maximumAverageSubtree function, this helper is only useful to this function.  It does not need to exposed to the outside, so it makes sense to hide it inside.

This is a common paradigm in Python.  Decorators use this all the time.  For example, a @timed decorator has an internal wrapper() function.

def timed(func):

    def wrapper(*argv, **kwargs):
        start_time = time.perf_counter()
        result = func(*argv, **kwargs)
        end_time = time.perf_counter()
        print(func.__name__, "took", end_time - start_time, "seconds")
        return result

    return wrapper


The function shouldn't be arbitrarily named; its name should reflect its purpose.  Here, we are wrapping a call to another function, so wrapper() makes sense.

Above, you have a helper() function.  That probably could be named better.  Maybe process_subtree(node).  But it is "scoped" inside maximumAverageSubtree(), so its name doesn't need to repeat that level of detail.