Online Book Reader

Home Category

Learning Python - Mark Lutz [241]

By Root 1562 0
you realize that Python would not otherwise generally know which enclosing scope to create a brand new name in. In the prior listing, should spam be assigned in tester, or the module outside? Because this is ambiguous, Python must resolve nonlocals at function creation time, not function call time.

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:

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)

Return Main Page Previous Page Next Page

®Online Book Reader