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:

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.