Learning Python - Mark Lutz [236]
X = 99 # Global scope name: not used
def f1():
X = 88 # Enclosing def local
def f2():
print(X) # Reference made in nested def
f2()
f1() # Prints 88: enclosing def local
First off, this is legal Python code: the def is simply an executable statement, which can appear anywhere any other statement can—including nested in another def. Here, the nested def runs while a call to the function f1 is running; it generates a function and assigns it to the name f2, a local variable within f1’s local scope. In a sense, f2 is a temporary function that lives only during the execution of (and is visible only to code in) the enclosing f1.
But notice what happens inside f2: when it prints the variable X, it refers to the X that lives in the enclosing f1 function’s local scope. Because functions can access names in all physically enclosing def statements, the X in f2 is automatically mapped to the X in f1, by the LEGB lookup rule.
This enclosing scope lookup works even if the enclosing function has already returned. For example, the following code defines a function that makes and returns another function:
def f1():
X = 88
def f2():
print(X) # Remembers X in enclosing def scope
return f2 # Return f2 but don't call it
action = f1() # Make, return function
action() # Call it now: prints 88
In this code, the call to action is really running the function we named f2 when f1 ran. f2 remembers the enclosing scope’s X in f1, even though f1 is no longer active.
Factory functions
Depending on whom you ask, this sort of behavior is also sometimes called a closure or factory function. These terms refer to a function object that remembers values in enclosing scopes regardless of whether those scopes are still present in memory. Although classes (described in Part VI of this book) are usually best at remembering state because they make it explicit with attribute assignments, such functions provide an alternative.
For instance, factory functions are sometimes used by programs that need to generate event handlers on the fly in response to conditions at runtime (e.g., user inputs that cannot be anticipated). Look at the following function, for example:
>>> def maker(N):
... def action(X): # Make and return action
... return X ** N # action retains N from enclosing scope
... return action
...
This defines an outer function that simply generates and returns a nested function, without calling it. If we call the outer function:
>>> f = maker(2) # Pass 2 to N
>>> f
what we get back is a reference to the generated nested function—the one created by running the nested def. If we now call what we got back from the outer function: >>> f(3) # Pass 3 to X, N remembers 2: 3 ** 2 9 >>> f(4) # 4 ** 2 16 it invokes the nested function—the one called action within maker. The most unusual part of this is that the nested function remembers integer 2, the value of the variable N in maker, even though maker has returned and exited by the time we call action. In effect, N from the enclosing local scope is retained as state information attached to action, and we get back its argument squared. If we now call the outer function again, we get back a new nested function with different state information attached. That is, we get the argument cubed instead of squared, but the original still squares as before: >>> g = maker(3) # g remembers 3, f remembers 2 >>> g(3) # 3 ** 3 27 >>> f(3) # 3 ** 2 9 This works because each call to a factory function like this gets its own set of state information. In our case, the function we assign to name g remembers 3, and f remembers 2, because each has its own state information retained by the variable N in maker. This is an advanced technique that you’re unlikely to see very often in most code, except among programmers with backgrounds in functional programming languages. On the other hand, enclosing scopes are often employed by lambda function-creation expressions (discussed later in this chapter)—because they are expressions, they are almost