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

@ -323,7 +323,7 @@ perform the relative import starting from the parent of the current
package. For example, code in the \module{A.B.C} module can do: package. For example, code in the \module{A.B.C} module can do:
\begin{verbatim} \begin{verbatim}
from . import D # Imports A.B.D from . import D # Imports A.B.D
from .. import E # Imports A.E from .. import E # Imports A.E
from ..F import G # Imports A.F.G from ..F import G # Imports A.F.G
\end{verbatim} \end{verbatim}
@ -431,7 +431,7 @@ def counter (maximum):
i = 0 i = 0
while i < maximum: while i < maximum:
yield i yield i
i += 1 i += 1
\end{verbatim} \end{verbatim}
When you call \code{counter(10)}, the result is an iterator that When you call \code{counter(10)}, the result is an iterator that
@ -473,11 +473,11 @@ def counter (maximum):
i = 0 i = 0
while i < maximum: while i < maximum:
val = (yield i) val = (yield i)
# If value provided, change counter # If value provided, change counter
if val is not None: if val is not None:
i = val i = val
else: else:
i += 1 i += 1
\end{verbatim} \end{verbatim}
And here's an example of changing the counter: And here's an example of changing the counter:
@ -578,33 +578,34 @@ Sugalski.}
%====================================================================== %======================================================================
\section{PEP 343: The 'with' statement} \section{PEP 343: The 'with' statement}
The \keyword{with} statement allows a clearer The \keyword{with} statement allows a clearer version of code that
version of code that uses \code{try...finally} blocks 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 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 then a subsection will examine the implementation details and how to
(called ``context managers'') that can be used with this statement. write objects (called ``context managers'') that can be used with this
Most people, who will only use \keyword{with} in company with an statement. Most people will only use \keyword{with} in company with
existing object, don't need to know these details and can existing objects that are documented to work as context managers, and
just use objects that are documented to work as context managers. don't need to know these details, so you can skip the subsection if
Authors of new context managers will need to understand the details of you like. Authors of new context managers will need to understand the
the underlying implementation. details of the underlying implementation.
The \keyword{with} statement is a new control-flow structure whose The \keyword{with} statement is a new control-flow structure whose
basic structure is: basic structure is:
\begin{verbatim} \begin{verbatim}
with expression as variable: with expression [as variable]:
with-block with-block
\end{verbatim} \end{verbatim}
The expression is evaluated, and it should result in a type of object 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 that's called a context manager. The context manager can return a
value that will be bound to the name \var{variable}. (Note carefully: value that can optionally be bound to the name \var{variable}. (Note
\var{variable} is \emph{not} assigned the result of \var{expression}. carefully: \var{variable} is \emph{not} assigned the result of
One method of the context manager is run before \var{with-block} is \var{expression}.) One method of the context manager is run before
executed, and another method is run after the block is done, even if \var{with-block} is executed, and another method is run after the
the block raised an exception. block is done, even if the block raised an exception.
To enable the statement in Python 2.5, you need To enable the statement in Python 2.5, you need
to add the following directive to your module: 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 from __future__ import with_statement
\end{verbatim} \end{verbatim}
Some standard Python objects can now behave as context managers. For The statement will always be enabled in Python 2.6.
example, file objects:
Some standard Python objects can now behave as context managers. File
objects are one example:
\begin{verbatim} \begin{verbatim}
with open('/etc/passwd', 'r') as f: with open('/etc/passwd', 'r') as f:
for line in f: for line in f:
print line print line
... more processing code ...
# f has been automatically closed at this point.
\end{verbatim} \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 The \module{threading} module's locks and condition variables
also support the \keyword{with} statement: also support the \keyword{with} statement:
@ -634,7 +640,7 @@ with lock:
... ...
\end{verbatim} \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 block is complete.
The \module{decimal} module's contexts, which encapsulate the desired The \module{decimal} module's contexts, which encapsulate the desired
@ -644,9 +650,8 @@ used as context managers.
\begin{verbatim} \begin{verbatim}
import decimal import decimal
v1 = decimal.Decimal('578')
# Displays with default precision of 28 digits # Displays with default precision of 28 digits
v1 = decimal.Decimal('578')
print v1.sqrt() print v1.sqrt()
with decimal.Context(prec=16): with decimal.Context(prec=16):
@ -657,9 +662,170 @@ with decimal.Context(prec=16):
\subsection{Writing Context Managers} \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 The new \module{contextlib} module provides some functions and a
decorator that are useful for writing context managers. decorator that are useful for writing context managers.
@ -670,7 +836,9 @@ Future versions will go into more detail.
\begin{seealso} \begin{seealso}
\seepep{343}{The ``with'' statement}{PEP written by \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} \end{seealso}
@ -1027,10 +1195,10 @@ Printing \code{index} results in the following output:
\begin{verbatim} \begin{verbatim}
defaultdict(<type 'list'>, {'c': ['cammin', 'che'], 'e': ['era'], defaultdict(<type 'list'>, {'c': ['cammin', 'che'], 'e': ['era'],
'd': ['del', 'di', 'diritta'], 'm': ['mezzo', 'mi'], 'd': ['del', 'di', 'diritta'], 'm': ['mezzo', 'mi'],
'l': ['la'], 'o': ['oscura'], 'n': ['nel', 'nostra'], 'l': ['la'], 'o': ['oscura'], 'n': ['nel', 'nostra'],
'p': ['per'], 's': ['selva', 'smarrita'], 'p': ['per'], 's': ['selva', 'smarrita'],
'r': ['ritrovai'], 'u': ['una'], 'v': ['vita', 'via']} 'r': ['ritrovai'], 'u': ['una'], 'v': ['vita', 'via']}
\end{verbatim} \end{verbatim}
The \class{deque} double-ended queue type supplied by the The \class{deque} double-ended queue type supplied by the
@ -1435,7 +1603,7 @@ h = hashlib.sha384()
h = hashlib.sha512() h = hashlib.sha512()
# Alternative form # Alternative form
h = hashlib.new('md5') # Provide algorithm as a string h = hashlib.new('md5') # Provide algorithm as a string
\end{verbatim} \end{verbatim}
Once a hash object has been created, its methods are the same as before: Once a hash object has been created, its methods are the same as before:
@ -1515,9 +1683,9 @@ c.execute('select * from stocks where symbol=?', ('IBM',))
# Larger example # Larger example
for t in (('2006-03-28', 'BUY', 'IBM', 1000, 45.00), for t in (('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
('2006-04-05', 'BUY', 'MSOFT', 1000, 72.00), ('2006-04-05', 'BUY', 'MSOFT', 1000, 72.00),
('2006-04-06', 'SELL', 'IBM', 500, 53.00), ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
): ):
c.execute('insert into stocks values (?,?,?,?,?)', t) c.execute('insert into stocks values (?,?,?,?,?)', t)
\end{verbatim} \end{verbatim}