Write most of the 'writing context managers' section. I'd like comments on it,

but wait for a few hours before you read it; I'm still revising it
    and will be tackling contextlib next.
Untabify
This commit is contained in:
Andrew M. Kuchling 2006-04-16 18:20:05 +00:00
parent cb284197f2
commit d058d0036a
1 changed files with 211 additions and 43 deletions

View File

@ -578,33 +578,34 @@ Sugalski.}
%======================================================================
\section{PEP 343: The 'with' statement}
The \keyword{with} statement allows a clearer
version of code that uses \code{try...finally} blocks
The \keyword{with} statement allows a clearer version of code that
uses \code{try...finally} blocks to ensure that clean-up code is
executed.
First, I'll discuss the statement as it will commonly be used, and
then I'll discuss the detailed implementation and how to write objects
(called ``context managers'') that can be used with this statement.
Most people, who will only use \keyword{with} in company with an
existing object, don't need to know these details and can
just use objects that are documented to work as context managers.
Authors of new context managers will need to understand the details of
the underlying implementation.
then a subsection will examine the implementation details and how to
write objects (called ``context managers'') that can be used with this
statement. Most people will only use \keyword{with} in company with
existing objects that are documented to work as context managers, and
don't need to know these details, so you can skip the subsection if
you like. Authors of new context managers will need to understand the
details of the underlying implementation.
The \keyword{with} statement is a new control-flow structure whose
basic structure is:
\begin{verbatim}
with expression as variable:
with expression [as variable]:
with-block
\end{verbatim}
The expression is evaluated, and it should result in a type of object
that's called a context manager. The context manager can return a
value that will be bound to the name \var{variable}. (Note carefully:
\var{variable} is \emph{not} assigned the result of \var{expression}.
One method of the context manager is run before \var{with-block} is
executed, and another method is run after the block is done, even if
the block raised an exception.
value that can optionally be bound to the name \var{variable}. (Note
carefully: \var{variable} is \emph{not} assigned the result of
\var{expression}.) One method of the context manager is run before
\var{with-block} is executed, and another method is run after the
block is done, even if the block raised an exception.
To enable the statement in Python 2.5, you need
to add the following directive to your module:
@ -613,17 +614,22 @@ to add the following directive to your module:
from __future__ import with_statement
\end{verbatim}
Some standard Python objects can now behave as context managers. For
example, file objects:
The statement will always be enabled in Python 2.6.
Some standard Python objects can now behave as context managers. File
objects are one example:
\begin{verbatim}
with open('/etc/passwd', 'r') as f:
for line in f:
print line
# f has been automatically closed at this point.
... more processing code ...
\end{verbatim}
After this statement has executed, the file object in \var{f} will
have been automatically closed at this point, even if the 'for' loop
raised an exception part-way through the block.
The \module{threading} module's locks and condition variables
also support the \keyword{with} statement:
@ -634,7 +640,7 @@ with lock:
...
\end{verbatim}
The lock is acquired before the block is executed, and released once
The lock is acquired before the block is executed, and always released once
the block is complete.
The \module{decimal} module's contexts, which encapsulate the desired
@ -644,9 +650,8 @@ used as context managers.
\begin{verbatim}
import decimal
v1 = decimal.Decimal('578')
# Displays with default precision of 28 digits
v1 = decimal.Decimal('578')
print v1.sqrt()
with decimal.Context(prec=16):
@ -657,9 +662,170 @@ with decimal.Context(prec=16):
\subsection{Writing Context Managers}
% XXX write this
Under the hood, the \keyword{with} statement is fairly complicated.
The interface demanded of context managers contains several methods.
A high-level explanation of the context management protocol is:
\begin{itemize}
\item The expression is evaluated and should result in an object
that's a context manager, meaning that it has a
\method{__context__()} method.
\item This object's \method{__context__()} method is called, and must
return a context object.
\item The context's \method{__enter__()} method is called.
The value returned is assigned to \var{VAR}. If no \code{as \var{VAR}}
clause is present, the value is simply discarded.
\item The code in \var{BLOCK} is executed.
\item If \var{BLOCK} raises an exception, the context object's
\method{__exit__(\var{type}, \var{value}, \var{traceback})} is called
with the exception's information, the same values returned by
\function{sys.exc_info()}. The method's return value
controls whether the exception is re-raised: any false value
re-raises the exception, and \code{True} will result in suppressing it.
You'll only rarely want to suppress the exception; the
author of the code containing the \keyword{with} statement will
never realize anything went wrong.
\item If \var{BLOCK} didn't raise an exception,
the context object's \method{__exit__()} is still called,
but \var{type}, \var{value}, and \var{traceback} are all \code{None}.
\end{itemize}
Let's think through an example. I won't present detailed code but
will only sketch the necessary code. The example will be writing a
context manager for a database that supports transactions.
(For people unfamiliar with database terminology: a set of changes to
the database are grouped into a transaction. Transactions can be
either committed, meaning that all the changes are written into the
database, or rolled back, meaning that the changes are all discarded
and the database is unchanged. See any database textbook for more
information.)
% XXX find a shorter reference?
Let's assume there's an object representing a database connection.
Our goal will be to let the user write code like this:
\begin{verbatim}
db_connection = DatabaseConnection()
with db_connection as cursor:
cursor.execute('insert into ...')
cursor.execute('delete from ...')
# ... more operations ...
\end{verbatim}
The transaction should either be committed if the code in the block
runs flawlessly, or rolled back if there's an exception.
First, the \class{DatabaseConnection} needs a \method{__context__()}
method. Sometimes an object can be its own context manager and can
simply return \code{self}; the \module{threading} module's lock objects
can do this. For our database example, though, we need to
create a new object; I'll call this class \class{DatabaseContext}.
Our \method{__context__()} must therefore look like this:
\begin{verbatim}
class DatabaseConnection:
...
def __context__ (self):
return DatabaseContext(self)
# Database interface
def cursor (self):
"Returns a cursor object and starts a new transaction"
def commit (self):
"Commits current transaction"
def rollback (self):
"Rolls back current transaction"
\end{verbatim}
The context needs the connection object so that the connection
object's \method{commit()} or \method{rollback()} methods can be
called:
\begin{verbatim}
class DatabaseContext:
def __init__ (self, connection):
self.connection = connection
\end{verbatim}
The \method {__enter__()} method is pretty easy, having only
to start a new transaction. In this example,
the resulting cursor object would be a useful result,
so the method will return it. The user can
then add \code{as cursor} to their \keyword{with} statement
to bind the cursor to a variable name.
\begin{verbatim}
class DatabaseContext:
...
def __enter__ (self):
# Code to start a new transaction
cursor = self.connection.cursor()
return cursor
\end{verbatim}
The \method{__exit__()} method is the most complicated because it's
where most of the work has to be done. The method has to check if an
exception occurred. If there was no exception, the transaction is
committed. The transaction is rolled back if there was an exception.
Here the code will just fall off the end of the function, returning
the default value of \code{None}. \code{None} is false, so the exception
will be re-raised automatically. If you wished, you could be more explicit
and add a \keyword{return} at the marked location.
\begin{verbatim}
class DatabaseContext:
...
def __exit__ (self, type, value, tb):
if tb is None:
# No exception, so commit
self.connection.commit()
else:
# Exception occurred, so rollback.
self.connection.rollback()
# return False
\end{verbatim}
\begin{comment}
% XXX should I give the code, or is the above explanation sufficient?
\pep{343} shows the code generated for a \keyword{with} statement. A
statement such as:
\begin{verbatim}
with EXPR as VAR:
BLOCK
\end{verbatim}
is translated into:
\begin{verbatim}
ctx = (EXPR).__context__()
exit = ctx.__exit__ # Not calling it yet
value = ctx.__enter__()
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(*sys.exc_info()):
raise
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(None, None, None)
\end{verbatim}
\end{comment}
This section still needs to be written.
The new \module{contextlib} module provides some functions and a
decorator that are useful for writing context managers.
@ -670,7 +836,9 @@ Future versions will go into more detail.
\begin{seealso}
\seepep{343}{The ``with'' statement}{PEP written by
Guido van Rossum and Nick Coghlan. }
Guido van Rossum and Nick Coghlan.
The PEP shows the code generated for a \keyword{with} statement,
which can be helpful in learning how context managers work.}
\end{seealso}