[Python-Dev] cpython: Close #13585: add contextlib.ExitStack to replace the ill-fated

Georg Brandl g.brandl at gmx.net
Mon May 21 19:45:14 CEST 2012


Am 21.05.2012 14:54, schrieb nick.coghlan:
> http://hg.python.org/cpython/rev/8ef66c73b1e1
> changeset:   77095:8ef66c73b1e1
> user:        Nick Coghlan <ncoghlan at gmail.com>
> date:        Mon May 21 22:54:43 2012 +1000
> summary:
>   Close #13585: add contextlib.ExitStack to replace the ill-fated contextlib.nested API
> 
> files:
>   Doc/library/contextlib.rst  |  279 +++++++++++++++++++++++-
>   Doc/whatsnew/3.3.rst        |   15 +
>   Lib/contextlib.py           |  126 ++++++++++-
>   Lib/test/test_contextlib.py |  123 ++++++++++
>   Misc/NEWS                   |    2 +
>   5 files changed, 539 insertions(+), 6 deletions(-)
> 
> 
> diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst
> --- a/Doc/library/contextlib.rst
> +++ b/Doc/library/contextlib.rst
> @@ -12,8 +12,11 @@
>  statement. For more information see also :ref:`typecontextmanager` and
>  :ref:`context-managers`.
>  
> -Functions provided:
>  
> +Utilities
> +---------
> +
> +Functions and classes provided:
>  
>  .. decorator:: contextmanager
>  
> @@ -168,6 +171,280 @@
>     .. versionadded:: 3.2
>  
>  
> +.. class:: ExitStack()
> +
> +   A context manager that is designed to make it easy to programmatically
> +   combine other context managers and cleanup functions, especially those
> +   that are optional or otherwise driven by input data.
> +
> +   For example, a set of files may easily be handled in a single with
> +   statement as follows::
> +
> +      with ExitStack() as stack:
> +          files = [stack.enter_context(open(fname)) for fname in filenames]
> +          # All opened files will automatically be closed at the end of
> +          # the with statement, even if attempts to open files later
> +          # in the list throw an exception
> +
> +   Each instance maintains a stack of registered callbacks that are called in
> +   reverse order when the instance is closed (either explicitly or implicitly
> +   at the end of a ``with`` statement). Note that callbacks are *not* invoked
> +   implicitly when the context stack instance is garbage collected.
> +
> +   This stack model is used so that context managers that acquire their
> +   resources in their ``__init__`` method (such as file objects) can be
> +   handled correctly.
> +
> +   Since registered callbacks are invoked in the reverse order of
> +   registration, this ends up behaving as if multiple nested ``with``
> +   statements had been used with the registered set of callbacks. This even
> +   extends to exception handling - if an inner callback suppresses or replaces
> +   an exception, then outer callbacks will be passed arguments based on that
> +   updated state.
> +
> +   This is a relatively low level API that takes care of the details of
> +   correctly unwinding the stack of exit callbacks. It provides a suitable
> +   foundation for higher level context managers that manipulate the exit
> +   stack in application specific ways.
> +
> +   .. method:: enter_context(cm)
> +
> +      Enters a new context manager and adds its :meth:`__exit__` method to
> +      the callback stack. The return value is the result of the context
> +      manager's own :meth:`__enter__` method.
> +
> +      These context managers may suppress exceptions just as they normally
> +      would if used directly as part of a ``with`` statement.
> +
> +   .. method:: push(exit)
> +
> +      Adds a context manager's :meth:`__exit__` method to the callback stack.
> +
> +      As ``__enter__`` is *not* invoked, this method can be used to cover
> +      part of an :meth:`__enter__` implementation with a context manager's own
> +      :meth:`__exit__` method.
> +
> +      If passed an object that is not a context manager, this method assumes
> +      it is a callback with the same signature as a context manager's
> +      :meth:`__exit__` method and adds it directly to the callback stack.
> +
> +      By returning true values, these callbacks can suppress exceptions the
> +      same way context manager :meth:`__exit__` methods can.
> +
> +      The passed in object is returned from the function, allowing this
> +      method to be used is a function decorator.
> +
> +   .. method:: callback(callback, *args, **kwds)
> +
> +      Accepts an arbitrary callback function and arguments and adds it to
> +      the callback stack.
> +
> +      Unlike the other methods, callbacks added this way cannot suppress
> +      exceptions (as they are never passed the exception details).
> +
> +      The passed in callback is returned from the function, allowing this
> +      method to be used is a function decorator.
> +
> +   .. method:: pop_all()
> +
> +      Transfers the callback stack to a fresh :class:`ExitStack` instance
> +      and returns it. No callbacks are invoked by this operation - instead,
> +      they will now be invoked when the new stack is closed (either
> +      explicitly or implicitly).
> +
> +      For example, a group of files can be opened as an "all or nothing"
> +      operation as follows::
> +
> +         with ExitStack() as stack:
> +             files = [stack.enter_context(open(fname)) for fname in filenames]
> +             close_files = stack.pop_all().close
> +             # If opening any file fails, all previously opened files will be
> +             # closed automatically. If all files are opened successfully,
> +             # they will remain open even after the with statement ends.
> +             # close_files() can then be invoked explicitly to close them all
> +
> +   .. method:: close()
> +
> +      Immediately unwinds the callback stack, invoking callbacks in the
> +      reverse order of registration. For any context managers and exit
> +      callbacks registered, the arguments passed in will indicate that no
> +      exception occurred.
> +
> +   .. versionadded:: 3.3

I'd prefer this versionadded a little more towards the top of the class
documentation, e.g. before the first method.  Otherwise it might get overlooked,
or taken as belonging to the close() method (the indentation suggests otherwise,
but that might not be enough cue for a quick read).

Georg



More information about the Python-Dev mailing list