Why nonlocal?

Given the extra complexity of nested functions, you might wonder what the fuss is about. Although it’s difficult to see in our small examples, state information becomes crucial in many programs. There are a variety of ways to “remember” information across function and method calls in Python. While there are tradeoffs for all, nonlocal does improve this story for enclosing scope references—the nonlocal statement allows multiple copies of changeable state to be retained in memory and addresses simple state-retention needs where classes may not be warranted.

As we saw in the prior section, the following code allows state to be retained and modified in an enclosing scope. Each call to tester creates a little self-contained package of changeable information, whose names do not clash with any other part of the program:

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

def tester(start):
    state = start                      # Each call gets its own state
    def nested(label):
        nonlocal state                 # Remembers state in enclosing scope
        print(label, state)
        state += 1                     # Allowed to change it if nonlocal
    return nested

F = tester(0)
F('spam')

Unfortunately, this code only works in Python 3.0. If you are using Python 2.6, other options are available, depending on your goals. The next three sections present some alternatives.

Shared state with globals

One usual prescription for achieving the nonlocal effect in 2.6 and earlier is to simply move the state out to the global scope (the enclosing module):

>>> def tester(start):
...     global state                   # Move it out to the module to change it
...     state = start                  # global allows changes in module scope
...     def nested(label):
...         global state
...         print(label, state)
...         state += 1
...     return nested
...
>>> F = tester(0)
>>> F('spam')                          # Each call increments shared global state
spam 0
>>> F('eggs')
eggs 1

This works in this case, but it requires global declarations in both functions and is prone to name collisions in the global scope (what if “state” is already being used?). A worse, and more subtle, problem is that it only allows for a single shared copy of the state information in the module scope—if we call tester again, we’ll wind up resetting the module’s state variable, such that prior calls will see their state overwritten:

>>> G = tester(42)                     # Resets state's single copy in global scope
>>> G('toast')
toast 42

>>> G('bacon')
bacon 43

>>> F('ham')                           # Oops -- my counter has been overwritten!
ham 44

As shown earlier, when using nonlocal instead of global, each call to tester remembers its own unique copy of the state object.

State with classes (preview)

The other prescription for changeable state information in 2.6 and earlier is to use classes with attributes to make state information access more explicit than the implicit magic of scope lookup rules. As an added benefit, each instance of a class gets a fresh copy of the state information, as a natural byproduct of Python’s object model.

We haven’t explored classes in detail yet, but as a brief preview, here is a reformulation of the tester/nested functions used earlier as a class—state is recorded in objects explicitly as they are created. To make sense of this code, you need to know that a def within a class like this works exactly like a def outside of a class, except that the function’s self argument automatically receives the implied subject of the call (an instance object created by calling the class itself):

>>> class tester:                          # Class-based alternative (see Part VI)
...     def __init__(self, start):         # On object construction,
...         self.state = start             # save state explicitly in new object
...     def nested(self, label):
...         print(label, self.state)       # Reference state explicitly
...         self.state += 1                # Changes are always allowed
...
>>> F = tester(0)                          # Create instance, invoke __init__
>>> F.nested('spam')                       # F is passed to self
spam 0
>>> F.nested('ham')
ham 1

>>> G = tester(42)                         # Each instance gets new copy of state
>>> G.nested('toast')                      # Changing one does not impact others
toast 42
>>> G.nested('bacon')
bacon 43

>>> F.nested('eggs')                       # F's state is where it left off
eggs 2
>>> F.state                                # State may be accessed outside class
3

With just slightly more magic, which we’ll delve into later in this book, we could also make our class look like a callable function using operator overloading. __call__ intercepts direct calls on an instance, so we don’t need to call a named method:

>>> class tester:
...     def __init__(self, start):
...         self.state = start
...     def __call__(self, label):         # Intercept direct instance calls
...         print(label, self.state)       # So .nested() not required
...         self.state += 1
...
>>> H = tester(99)
>>> H('juice')                             # Invokes __call__
juice 99
>>> H('pancakes')
pancakes 100

Don’t sweat the details in this code too much at this point in the book; we’ll explore classes in depth in Part VI and will look at specific operator overloading tools like __call__ in Chapter 29, so you may wish to file this code away for future reference. The point here is that classes can make state information more obvious, by leveraging explicit attribute assignment instead of scope lookups.

While using classes for state information is generally a good rule of thumb to follow, they might be overkill in cases like this, where state is a single counter. Such trivial state cases are more common than you might think; in such contexts, nested defs are sometimes more lightweight than coding classes, especially if you’re not familiar with OOP yet. Moreover, there are some scenarios in which nested defs may actually work better than classes (see the description of method decorators in Chapter 38 for an example that is far beyond this chapter’s scope).

State with function attributes

As a final state-retention option, we can also sometimes achieve the same effect as nonlocals with function attributes—user-defined names attached to functions directly. Here’s a final version of our example based on this technique—it replaces a nonlocal with an attribute attached to the nested function. Although this scheme may not be as intuitive to some, it also allows the state variable to be accessed outside the nested function (with nonlocals, we can only see state variables within the nested def):

>>> def tester(start):
...     def nested(label):
...         print(label, nested.state)     # nested is in enclosing scope
...         nested.state += 1              # Change attr, not nested itself
...     nested.state = start               # Initial state after func defined
...     return nested
...
>>> F = tester(0)
>>> F('spam')                              # F is a 'nested' with state attached
spam 0
>>> F('ham')
ham 1
>>> F.state                                # Can access state outside functions too
2
>>>
>>> G = tester(42)                         # G has own state, doesn't overwrite F's
>>> G('eggs')
eggs 42
>>> F('ham')
ham 2

This code relies on the fact that the function name nested is a local variable in the tester scope enclosing nested; as such, it can be referenced freely inside nested. This code also relies on the fact that changing an object in-place is not an assignment to a name; when it increments nested.state, it is changing part of the object nested references, not the name nested itself. Because we’re not really assigning a name in the enclosing scope, no nonlocal is needed.

As you can see, globals, nonlocals, classes, and function attributes all offer state-retention options. Globals only support shared data, classes require a basic knowledge of OOP, and both classes and function attributes allow state to be accessed outside the nested function itself. As usual, the best tool for your program depends upon your program’s goals.[40]

 


[40] Function attributes are supported in both Python 2.6 and 3.X. We'll explore them further in Chapter 19, and revisit all the state options introduced here in Chapter 38 in a more realistic context. Also note that it's possible to change a mutable object in the enclosing scope in 2.X and 3.X without declaring its name nonlocal (e.g, state=[start] in tester, state[0]+=1 in nested), though it's perhaps more obscure than either function attributes or 3.X's nonlocal.