Learning Python - Mark Lutz [242]
>>> 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