diff options
-rw-r--r-- | Doc/lib/libcontextlib.tex | 73 | ||||
-rw-r--r-- | Doc/lib/libstdtypes.tex | 83 | ||||
-rw-r--r-- | Doc/ref/ref3.tex | 49 | ||||
-rw-r--r-- | Doc/ref/ref7.tex | 16 | ||||
-rw-r--r-- | Lib/contextlib.py | 31 | ||||
-rw-r--r-- | Lib/decimal.py | 4 | ||||
-rw-r--r-- | Lib/test/test_contextlib.py | 46 |
7 files changed, 163 insertions, 139 deletions
diff --git a/Doc/lib/libcontextlib.tex b/Doc/lib/libcontextlib.tex index 2a9eb0e..f212174 100644 --- a/Doc/lib/libcontextlib.tex +++ b/Doc/lib/libcontextlib.tex @@ -11,18 +11,19 @@ This module provides utilities for common tasks involving the Functions provided: -\begin{funcdesc}{contextmanager}{func} -This function is a decorator that can be used to define context managers -for use with the \keyword{with} statement, without needing to create a -class or separate \method{__enter__()} and \method{__exit__()} methods. +\begin{funcdesc}{context}func} +This function is a decorator that can be used to define a factory +function for \keyword{with} statement context objects, without +needing to create a class or separate \method{__enter__()} and +\method{__exit__()} methods. A simple example: \begin{verbatim} from __future__ import with_statement -from contextlib import contextmanager +from contextlib import contextfactory -@contextmanager +@contextfactory def tag(name): print "<%s>" % name yield @@ -36,9 +37,10 @@ foo </h1> \end{verbatim} -When called, the decorated function must return a generator-iterator. -This iterator must yield exactly one value, which will be bound to the -targets in the \keyword{with} statement's \keyword{as} clause, if any. +The function being decorated must return a generator-iterator when +called. This iterator must yield exactly one value, which will be +bound to the targets in the \keyword{with} statement's \keyword{as} +clause, if any. At the point where the generator yields, the block nested in the \keyword{with} statement is executed. The generator is then resumed @@ -53,20 +55,20 @@ reraise that exception. Otherwise the \keyword{with} statement will treat the exception as having been handled, and resume execution with the statement immediately following the \keyword{with} statement. -Note that you can use \code{@contextmanager} to define a context -specifier's \method{__context__} method. This is usually more +Note that you can use \code{@contextfactory} to define a context +manager's \method{__context__} method. This is usually more convenient than creating another class just to serve as a context -manager. For example: +object. For example: \begin{verbatim} from __future__ import with_statement -from contextlib import contextmanager +from contextlib import contextfactory class Tag: def __init__(self, name): self.name = name - @contextmanager + @contextfactory def __context__(self): print "<%s>" % self.name yield self @@ -83,7 +85,7 @@ hello from <__main__.Tag instance at 0x402ce8ec> \end{funcdesc} \begin{funcdesc}{nested}{ctx1\optional{, ctx2\optional{, ...}}} -Combine multiple context specifiers into a single nested context manager. +Combine multiple context managers into a single nested context manager. Code like this: @@ -104,12 +106,12 @@ with A as X: \end{verbatim} Note that if the \method{__exit__()} method of one of the nested -context managers indicates an exception should be suppressed, no +context objects indicates an exception should be suppressed, no exception information will be passed to any remaining outer context -managers. Similarly, if the \method{__exit__()} method of one of the -nested context managers raises an exception, any previous exception +objects. Similarly, if the \method{__exit__()} method of one of the +nested context objects raises an exception, any previous exception state will be lost; the new exception will be passed to the -\method{__exit__()} methods of any remaining outer context managers. +\method{__exit__()} methods of any remaining outer context objects. In general, \method{__exit__()} methods should avoid raising exceptions, and in particular they should not re-raise a passed-in exception. @@ -117,13 +119,13 @@ passed-in exception. \label{context-closing} \begin{funcdesc}{closing}{thing} -Return a context manager that closes \var{thing} upon completion of the +Return a context that closes \var{thing} upon completion of the block. This is basically equivalent to: \begin{verbatim} -from contextlib import contextmanager +from contextlib import contextfactory -@contextmanager +@contextfactory def closing(thing): try: yield thing @@ -137,14 +139,33 @@ from __future__ import with_statement from contextlib import closing import codecs -with closing(urllib.urlopen('http://www.python.org')) as f: - for line in f: +with closing(urllib.urlopen('http://www.python.org')) as page: + for line in page: print line \end{verbatim} -without needing to explicitly close \code{f}. Even if an error occurs, -\code{f.close()} will be called when the \keyword{with} block is exited. +without needing to explicitly close \code{page}. Even if an error +occurs, \code{page.close()} will be called when the \keyword{with} +block is exited. +Context managers with a close method can use this context factory +directly without needing to implement their own +\method{__context__()} method. +\begin{verbatim} +from __future__ import with_statement +from contextlib import closing + +class MyClass: + def close(self): + print "Closing", self + __context__ = closing + +>>> with MyClass() as x: +... print "Hello from", x +... +Hello from <__main__.MyClass instance at 0xb7df02ec> +Closing <__main__.MyClass instance at 0xb7df02ec> +\end{verbatim} \end{funcdesc} \begin{seealso} diff --git a/Doc/lib/libstdtypes.tex b/Doc/lib/libstdtypes.tex index ea950c8..50be0fa 100644 --- a/Doc/lib/libstdtypes.tex +++ b/Doc/lib/libstdtypes.tex @@ -1756,59 +1756,59 @@ implemented in C will have to provide a writable \subsection{Context Types \label{typecontext}} \versionadded{2.5} -\index{context specification protocol} +\index{with statement context protocol} \index{context management protocol} -\index{protocol!context specification} +\index{protocol!with statement context} \index{protocol!context management} Python's \keyword{with} statement supports the concept of a runtime -context defined by a context specifier. This is implemented using +context defined by a context manager. This is implemented using three distinct methods; these are used to allow user-defined -classes to define a context. +classes to define a runtime context. -The \dfn{context specification protocol} consists of a single -method that needs to be provided for a context specifier object to +The \dfn{context management protocol} consists of a single +method that needs to be provided for a context manager object to define a runtime context: -\begin{methoddesc}[context specifier]{__context__}{} - Return a context manager object. The object is required to support - the context management protocol described below. If an object - supports different kinds of runtime context, additional methods can - be provided to specifically request context managers for those - kinds of context. (An example of an object supporting multiple kinds - of context would be a synchronisation object which supported both - a locked context for normal thread synchronisation and an unlocked - context to temporarily release a held lock while performing a - potentially long running operation) +\begin{methoddesc}[context manager]{__context__}{} + Return a with statement context object. The object is required to + support the with statement context protocol described below. If an + object supports different kinds of runtime context, additional + methods can be provided to specifically request context objects for + those kinds of runtime context. (An example of an object supporting + multiple kinds of context would be a synchronisation object which + supported both a locked context for normal thread synchronisation + and an unlocked context to temporarily release a held lock while + performing a potentially long running operation) \end{methoddesc} -The context manager objects themselves are required to support the +The with statement context objects themselves are required to support the following three methods, which together form the -\dfn{context management protocol}: +\dfn{with statement context protocol}: -\begin{methoddesc}[context manager]{__context__}{} - Return the context manager object itself. This is required to - allow both context specifiers and context managers to be used with - the \keyword{with} statement. +\begin{methoddesc}[with statement context]{__context__}{} + Return the context object itself. This is required to allow both + context objects and context managers to be used in a \keyword{with} + statement. \end{methoddesc} -\begin{methoddesc}[context manager]{__enter__}{} +\begin{methoddesc}[with statement context]{__enter__}{} Enter the runtime context and return either the defining context - specifier or another object related to the runtime context. The value + manager or another object related to the runtime context. The value returned by this method is bound to the identifier in the \keyword{as} clause of \keyword{with} statements using this context. - (An example of a context with a context manager that returns the - original context specifier is file objects, which are returned from - __enter__() to allow \function{open()} to be used directly in a with - statement. An example of a context manager that returns a related + (An example of a context object that returns the original context + manager is file objects, which are returned from __enter__() to + allow \function{open()} to be used directly in a with + statement. An example of a context object that returns a related object is \code{decimal.Context} which sets the active decimal - context to a copy of the context specifier and then returns the copy - to allow changes to be made to the current decimal context in the - body of the \keyword{with} statement) without affecting code outside + context to a copy of the context manager and then returns the copy. + This allows changes to be made to the current decimal context in the + body of the \keyword{with} statement without affecting code outside the \keyword{with} statement). \end{methoddesc} -\begin{methoddesc}[context manager]{__exit__}{exc_type, exc_val, exc_tb} +\begin{methoddesc}[with statement context]{__exit__}{exc_type, exc_val, exc_tb} Exit the runtime context and return a Boolean flag indicating if any expection that occurred should be suppressed. If an exception occurred while executing the body of the \keyword{with} statement, the @@ -1829,19 +1829,18 @@ following three methods, which together form the \method{__exit__()} method has actually failed. \end{methoddesc} -Python defines several context specifiers and managers to support +Python defines several context objects and managers to support easy thread synchronisation, prompt closure of files or other objects, and thread-safe manipulation of the decimal arithmetic context. The specific types are not important beyond their -implementation of the context specification and context -management protocols. - -Python's generators and the \code{contextlib.contextmanager} -decorator provide a convenient way to implement the context -specification and context management protocols. If a context -specifier's \method{__context__()} method is implemented as a -generator decorated with the \code{contextlib.contextmanager} -decorator, it will automatically return a context manager +implementation of the context management and with statement context +protocols. + +Python's generators and the \code{contextlib.contextfactory} decorator +provide a convenient way to implement these protocols. If a context +manager's \method{__context__()} method is implemented as a +generator decorated with the \code{contextlib.contextfactory} +decorator, it will automatically return a with statement context object supplying the necessary \method{__context__()}, \method{__enter__()} and \method{__exit__()} methods. diff --git a/Doc/ref/ref3.tex b/Doc/ref/ref3.tex index b1e1ee9..7b4089d 100644 --- a/Doc/ref/ref3.tex +++ b/Doc/ref/ref3.tex @@ -2112,59 +2112,60 @@ implement a \method{__coerce__()} method, for use by the built-in \end{itemize} -\subsection{Context Specifiers and Managers\label{context-managers}} +\subsection{With Statement Contexts and Context Managers\label{context-managers}} \versionadded{2.5} -A \dfn{context specifier} is an object that defines the runtime +A \dfn{context manager} is an object that defines the runtime context to be established when executing a \keyword{with} -statement. The context specifier provides a \dfn{context manager} -which manages the entry into, and the exit from, the desired -runtime context for the execution of the block of code. Context -managers are normally invoked using the \keyword{with} statement -(described in section~\ref{with}), but can also be used by -directly invoking their methods. +statement. The context manager provides a +\dfn{with statement context object} which manages the entry into, +and the exit from, the desired runtime context for the execution +of the block of code. Context managers are normally invoked using +the \keyword{with} statement (described in section~\ref{with}), but +can also be used by directly invoking their methods. \stindex{with} \index{context manager} -\index{context specifier} +\index{context (with statement)} +\index{with statement context} -Typical uses of context specifiers and managers include saving and +Typical uses of context managers and contexts include saving and restoring various kinds of global state, locking and unlocking resources, closing opened files, etc. -For more information on context specifiers and context manager objects, +For more information on context managers and context objects, see ``\ulink{Context Types}{../lib/typecontext.html}'' in the \citetitle[../lib/lib.html]{Python Library Reference}. -\begin{methoddesc}[context specifier]{__context__}{self} +\begin{methoddesc}[context manager]{__context__}{self} Invoked when the object is used as the context expression of a \keyword{with} statement. The returned object must implement \method{__enter__()} and \method{__exit__()} methods. -Context specifiers written in Python can also implement this method +Context managers written in Python can also implement this method using a generator function decorated with the -\function{contextlib.contextmanager} decorator, as this can be simpler +\function{contextlib.contextfactory} decorator, as this can be simpler than writing individual \method{__enter__()} and \method{__exit__()} methods on a separate object when the state to be managed is complex. -Context manager objects also need to implement this method; they are -required to return themselves (that is, this method will simply +With statement context objects also need to implement this method; they +are required to return themselves (that is, this method will simply return \var{self}). \end{methoddesc} -\begin{methoddesc}[context manager]{__enter__}{self} -Enter the context managed by this object. The \keyword{with} statement -will bind this method's return value to the target(s) specified in the -\keyword{as} clause of the statement, if any. +\begin{methoddesc}[with statement context]{__enter__}{self} +Enter the runtime context related to this object. The \keyword{with} +statement will bind this method's return value to the target(s) +specified in the \keyword{as} clause of the statement, if any. \end{methoddesc} \begin{methoddesc}[context manager]{__exit__} {self, exc_type, exc_value, traceback} -Exit the context managed by this object. The parameters describe the -exception that caused the context to be exited. If the context was -exited without an exception, all three arguments will be -\constant{None}. +Exit the runtime context related to this object. The parameters +describe the exception that caused the context to be exited. If +the context was exited without an exception, all three arguments +will be \constant{None}. If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a diff --git a/Doc/ref/ref7.tex b/Doc/ref/ref7.tex index 0c59847..180e22f 100644 --- a/Doc/ref/ref7.tex +++ b/Doc/ref/ref7.tex @@ -315,10 +315,10 @@ statement to generate exceptions may be found in section~\ref{raise}. \versionadded{2.5} The \keyword{with} statement is used to wrap the execution of a block -with methods defined by a context specifier or manager (see -section~\ref{context-managers}). This allows common +with methods defined by a context manager or with statement context +object (see section~\ref{context-managers}). This allows common \keyword{try}...\keyword{except}...\keyword{finally} usage patterns to -be encapsulated as context specifiers or managers for convenient reuse. +be encapsulated for convenient reuse. \begin{productionlist} \production{with_stmt} @@ -329,12 +329,12 @@ The execution of the \keyword{with} statement proceeds as follows: \begin{enumerate} -\item The expression is evaluated, to obtain a context specifier. +\item The context expression is evaluated, to obtain a context manager. -\item The context specifier's \method{__context__()} method is -invoked to obtain a context manager object. +\item The context manger's \method{__context__()} method is +invoked to obtain a with statement context object. -\item The context manager's \method{__enter__()} method is invoked. +\item The context object's \method{__enter__()} method is invoked. \item If a target list was included in the \keyword{with} statement, the return value from \method{__enter__()} is assigned to it. @@ -347,7 +347,7 @@ an error occurring within the suite would be. See step 6 below.} \item The suite is executed. -\item The context manager's \method{__exit__()} method is invoked. If +\item The context object's \method{__exit__()} method is invoked. If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to \method{__exit__()}. Otherwise, three \constant{None} arguments are supplied. diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 157b4cc..b2902a4 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -2,10 +2,10 @@ import sys -__all__ = ["contextmanager", "nested", "closing"] +__all__ = ["contextfactory", "nested", "closing"] -class GeneratorContextManager(object): - """Helper for @contextmanager decorator.""" +class GeneratorContext(object): + """Helper for @contextfactory decorator.""" def __init__(self, gen): self.gen = gen @@ -48,8 +48,8 @@ class GeneratorContextManager(object): raise -def contextmanager(func): - """@contextmanager decorator. +def contextfactory(func): + """@contextfactory decorator. Typical usage: @@ -77,7 +77,7 @@ def contextmanager(func): """ def helper(*args, **kwds): - return GeneratorContextManager(func(*args, **kwds)) + return GeneratorContext(func(*args, **kwds)) try: helper.__name__ = func.__name__ helper.__doc__ = func.__doc__ @@ -87,7 +87,7 @@ def contextmanager(func): return helper -@contextmanager +@contextfactory def nested(*contexts): """Support multiple context managers in a single with-statement. @@ -133,9 +133,8 @@ def nested(*contexts): raise exc[0], exc[1], exc[2] -@contextmanager -def closing(thing): - """Context manager to automatically close something at the end of a block. +class closing(object): + """Context to automatically close something at the end of a block. Code like this: @@ -151,7 +150,11 @@ def closing(thing): f.close() """ - try: - yield thing - finally: - thing.close() + def __init__(self, thing): + self.thing = thing + def __context__(self): + return self + def __enter__(self): + return self.thing + def __exit__(self, *exc_info): + self.thing.close() diff --git a/Lib/decimal.py b/Lib/decimal.py index 967f101..875e38a 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -2173,7 +2173,7 @@ for name in rounding_functions: del name, val, globalname, rounding_functions -class ContextManager(object): +class WithStatementContext(object): """Helper class to simplify Context management. Sample usage: @@ -2249,7 +2249,7 @@ class Context(object): return ', '.join(s) + ')' def __context__(self): - return ContextManager(self.copy()) + return WithStatementContext(self.copy()) def clear_flags(self): """Reset all flags to zero""" diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 1a70997..53f23b2 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -13,9 +13,9 @@ from test.test_support import run_suite class ContextManagerTestCase(unittest.TestCase): - def test_contextmanager_plain(self): + def test_contextfactory_plain(self): state = [] - @contextmanager + @contextfactory def woohoo(): state.append(1) yield 42 @@ -26,9 +26,9 @@ class ContextManagerTestCase(unittest.TestCase): state.append(x) self.assertEqual(state, [1, 42, 999]) - def test_contextmanager_finally(self): + def test_contextfactory_finally(self): state = [] - @contextmanager + @contextfactory def woohoo(): state.append(1) try: @@ -47,8 +47,8 @@ class ContextManagerTestCase(unittest.TestCase): self.fail("Expected ZeroDivisionError") self.assertEqual(state, [1, 42, 999]) - def test_contextmanager_no_reraise(self): - @contextmanager + def test_contextfactory_no_reraise(self): + @contextfactory def whee(): yield ctx = whee().__context__() @@ -56,8 +56,8 @@ class ContextManagerTestCase(unittest.TestCase): # Calling __exit__ should not result in an exception self.failIf(ctx.__exit__(TypeError, TypeError("foo"), None)) - def test_contextmanager_trap_yield_after_throw(self): - @contextmanager + def test_contextfactory_trap_yield_after_throw(self): + @contextfactory def whoo(): try: yield @@ -69,9 +69,9 @@ class ContextManagerTestCase(unittest.TestCase): RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None ) - def test_contextmanager_except(self): + def test_contextfactory_except(self): state = [] - @contextmanager + @contextfactory def woohoo(): state.append(1) try: @@ -86,14 +86,14 @@ class ContextManagerTestCase(unittest.TestCase): raise ZeroDivisionError(999) self.assertEqual(state, [1, 42, 999]) - def test_contextmanager_attribs(self): + def test_contextfactory_attribs(self): def attribs(**kw): def decorate(func): for k,v in kw.items(): setattr(func,k,v) return func return decorate - @contextmanager + @contextfactory @attribs(foo='bar') def baz(spam): """Whee!""" @@ -106,13 +106,13 @@ class NestedTestCase(unittest.TestCase): # XXX This needs more work def test_nested(self): - @contextmanager + @contextfactory def a(): yield 1 - @contextmanager + @contextfactory def b(): yield 2 - @contextmanager + @contextfactory def c(): yield 3 with nested(a(), b(), c()) as (x, y, z): @@ -122,14 +122,14 @@ class NestedTestCase(unittest.TestCase): def test_nested_cleanup(self): state = [] - @contextmanager + @contextfactory def a(): state.append(1) try: yield 2 finally: state.append(3) - @contextmanager + @contextfactory def b(): state.append(4) try: @@ -148,7 +148,7 @@ class NestedTestCase(unittest.TestCase): def test_nested_right_exception(self): state = [] - @contextmanager + @contextfactory def a(): yield 1 class b(object): @@ -172,10 +172,10 @@ class NestedTestCase(unittest.TestCase): self.fail("Didn't raise ZeroDivisionError") def test_nested_b_swallows(self): - @contextmanager + @contextfactory def a(): yield - @contextmanager + @contextfactory def b(): try: yield @@ -189,7 +189,7 @@ class NestedTestCase(unittest.TestCase): self.fail("Didn't swallow ZeroDivisionError") def test_nested_break(self): - @contextmanager + @contextfactory def a(): yield state = 0 @@ -201,7 +201,7 @@ class NestedTestCase(unittest.TestCase): self.assertEqual(state, 1) def test_nested_continue(self): - @contextmanager + @contextfactory def a(): yield state = 0 @@ -213,7 +213,7 @@ class NestedTestCase(unittest.TestCase): self.assertEqual(state, 3) def test_nested_return(self): - @contextmanager + @contextfactory def a(): try: yield |