Online Book Reader

Home Category

Learning Python - Mark Lutz [462]

By Root 1694 0

...more code here...

finally:

myfile.close()

We won’t cover Python’s multithreading modules in this book (for more on that topic, see follow-up application-level texts such as Programming Python), but the lock and condition synchronization objects they define may also be used with the with statement, because they support the context management protocol:

lock = threading.Lock()

with lock:

# critical section of code

...access shared resources...

Here, the context management machinery guarantees that the lock is automatically acquired before the block is executed and released once the block is complete, regardless of exception outcomes.

As introduced in Chapter 5, the decimal module also uses context managers to simplify saving and restoring the current decimal context, which specifies the precision and rounding characteristics for calculations:

with decimal.localcontext() as ctx:

ctx.prec = 2

x = decimal.Decimal('1.00') / decimal.Decimal('3.00')

After this statement runs, the current thread’s context manager state is automatically restored to what it was before the statement began. To do the same with a try/finally, we would need to save the context before and restore it manually.

The Context Management Protocol

Although some built-in types come with context managers, we can also write new ones of our own. To implement context managers, classes use special methods that fall into the operator overloading category to tap into the with statement. The interface expected of objects used in with statements is somewhat complex, and most programmers only need to know how to use existing context managers. For tool builders who might want to write new application-specific context managers, though, let’s take a quick look at what’s involved.

Here’s how the with statement actually works:

The expression is evaluated, resulting in an object known as a context manager that must have __enter__ and __exit__ methods.

The context manager’s __enter__ method is called. The value it returns is assigned to the variable in the as clause if present, or simply discarded otherwise.

The code in the nested with block is executed.

If the with block raises an exception, the __exit__(type, value, traceback) method is called with the exception details. Note that these are the same values returned by sys.exc_info, described in the Python manuals and later in this part of the book. If this method returns a false value, the exception is reraised; otherwise, the exception is terminated. The exception should normally be reraised so that it is propagated outside the with statement.

If the with block does not raise an exception, the __exit__ method is still called, but its type, value, and traceback arguments are all passed in as None.

Let’s look at a quick demo of the protocol in action. The following defines a context manager object that traces the entry and exit of the with block in any with statement it is used for:

class TraceBlock:

def message(self, arg):

print('running', arg)

def __enter__(self):

print('starting with block')

return self

def __exit__(self, exc_type, exc_value, exc_tb):

if exc_type is None:

print('exited normally\n')

else:

print('raise an exception!', exc_type)

return False # Propagate

with TraceBlock() as action:

action.message('test 1')

print('reached')

with TraceBlock() as action:

action.message('test 2')

raise TypeError

print('not reached')

Notice that this class’s __exit__ method returns False to propagate the exception; deleting the return statement would have the same effect, as the default None return value of functions is False by definition. Also notice that the __enter__ method returns self as the object to assign to the as variable; in other use cases, this might return a completely different object instead.

When run, the context manager traces the entry and exit of the with statement block with its __enter__ and __exit__ methods. Here’s the script in action being run under Python 3.0 (it runs in 2.6, too, but prints some extra tuple parentheses):

% python withas.py

Return Main Page Previous Page Next Page

®Online Book Reader