Binding in Recursive Functions#

We have seen that Python searches in LEGM order (local, enclosing, global, and finally built-in scopes), and we know that calling a function creates a stack frame for its namespace. The local scope is the namespace in the top stack frame.

While we have seen each of these facts before, it may be worthwhile to revisit our palindrome function to see how scopes work in a recursive function. Recall that we created a wrapper function called palindrome to get started and delegate the main work to _palindrome (with a leading underscore _ to indicate that it is not the public interface). We’ll focus on the actual recursive function _palindrome.

def _palindrome(word: list[str], first: int, last: int) -> bool: 
    """True if word[first:last] is a palindrome.
    first and last must be non-negative integers.
    """
    # Base cases
    if last - first < 1:
        return True
    # Recursive case
    x = word[first]
    y = word[last]
    return x == y and _palindrome(word, first+1, last-1)

word = ['n', 'o', 'o', 'n']
_palindrome(word, 0, len(word) - 1)
True

_palindrome will be called three times: _palindrome(word, 0, 3), _palindrome(word, 1, 2), and then _palindrome(word, 2, 1). The third call reaches the base case, ending the recursion. Let’s consider the stack and scopes at the third call:

Memory of  with three levels of recursion

Each execution of _palindrome has its own namespace in a stack frame. The top stack frame holds the namespace for the local scope. In this namespace,
first has value 2 and last has value 1, which makes the condition last - first < 1 true (the base case of the recursion).
As the same value word is passed as arguments through the recursive calls, the word value in each activation of _palindrome is a reference to the same list. In other words, although each activation of _palindrome has a local variable word, they are all aliases.