Georg Brandl | 0eaab97 | 2009-06-08 08:00:22 +0000 | [diff] [blame] | 1 | :mod:`contextlib` --- Utilities for :keyword:`with`\ -statement contexts |
| 2 | ======================================================================== |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 3 | |
| 4 | .. module:: contextlib |
| 5 | :synopsis: Utilities for with-statement contexts. |
| 6 | |
Raymond Hettinger | 1048094 | 2011-01-10 03:26:08 +0000 | [diff] [blame] | 7 | **Source code:** :source:`Lib/contextlib.py` |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 8 | |
Raymond Hettinger | 4f707fd | 2011-01-10 19:54:11 +0000 | [diff] [blame] | 9 | -------------- |
| 10 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 11 | This module provides utilities for common tasks involving the :keyword:`with` |
| 12 | statement. For more information see also :ref:`typecontextmanager` and |
| 13 | :ref:`context-managers`. |
| 14 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 15 | |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 16 | Utilities |
| 17 | --------- |
| 18 | |
| 19 | Functions and classes provided: |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 20 | |
Georg Brandl | 8a1caa2 | 2010-07-29 16:01:11 +0000 | [diff] [blame] | 21 | .. decorator:: contextmanager |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 22 | |
Christian Heimes | d8654cf | 2007-12-02 15:22:16 +0000 | [diff] [blame] | 23 | This function is a :term:`decorator` that can be used to define a factory |
| 24 | function for :keyword:`with` statement context managers, without needing to |
| 25 | create a class or separate :meth:`__enter__` and :meth:`__exit__` methods. |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 26 | |
| 27 | A simple example (this is not recommended as a real way of generating HTML!):: |
| 28 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 29 | from contextlib import contextmanager |
| 30 | |
| 31 | @contextmanager |
| 32 | def tag(name): |
Georg Brandl | 6911e3c | 2007-09-04 07:15:32 +0000 | [diff] [blame] | 33 | print("<%s>" % name) |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 34 | yield |
Georg Brandl | 6911e3c | 2007-09-04 07:15:32 +0000 | [diff] [blame] | 35 | print("</%s>" % name) |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 36 | |
| 37 | >>> with tag("h1"): |
Georg Brandl | 6911e3c | 2007-09-04 07:15:32 +0000 | [diff] [blame] | 38 | ... print("foo") |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 39 | ... |
| 40 | <h1> |
| 41 | foo |
| 42 | </h1> |
| 43 | |
Georg Brandl | 9afde1c | 2007-11-01 20:32:30 +0000 | [diff] [blame] | 44 | The function being decorated must return a :term:`generator`-iterator when |
| 45 | called. This iterator must yield exactly one value, which will be bound to |
| 46 | the targets in the :keyword:`with` statement's :keyword:`as` clause, if any. |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 47 | |
| 48 | At the point where the generator yields, the block nested in the :keyword:`with` |
| 49 | statement is executed. The generator is then resumed after the block is exited. |
| 50 | If an unhandled exception occurs in the block, it is reraised inside the |
| 51 | generator at the point where the yield occurred. Thus, you can use a |
| 52 | :keyword:`try`...\ :keyword:`except`...\ :keyword:`finally` statement to trap |
| 53 | the error (if any), or ensure that some cleanup takes place. If an exception is |
| 54 | trapped merely in order to log it or to perform some action (rather than to |
| 55 | suppress it entirely), the generator must reraise that exception. Otherwise the |
| 56 | generator context manager will indicate to the :keyword:`with` statement that |
| 57 | the exception has been handled, and execution will resume with the statement |
| 58 | immediately following the :keyword:`with` statement. |
| 59 | |
Nick Coghlan | 0ded3e3 | 2011-05-05 23:49:25 +1000 | [diff] [blame] | 60 | :func:`contextmanager` uses :class:`ContextDecorator` so the context managers |
| 61 | it creates can be used as decorators as well as in :keyword:`with` statements. |
| 62 | When used as a decorator, a new generator instance is implicitly created on |
| 63 | each function call (this allows the otherwise "one-shot" context managers |
| 64 | created by :func:`contextmanager` to meet the requirement that context |
| 65 | managers support multiple invocations in order to be used as decorators). |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 66 | |
| 67 | .. versionchanged:: 3.2 |
| 68 | Use of :class:`ContextDecorator`. |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 69 | |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 70 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 71 | .. function:: closing(thing) |
| 72 | |
| 73 | Return a context manager that closes *thing* upon completion of the block. This |
| 74 | is basically equivalent to:: |
| 75 | |
| 76 | from contextlib import contextmanager |
| 77 | |
| 78 | @contextmanager |
| 79 | def closing(thing): |
| 80 | try: |
| 81 | yield thing |
| 82 | finally: |
| 83 | thing.close() |
| 84 | |
| 85 | And lets you write code like this:: |
| 86 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 87 | from contextlib import closing |
Georg Brandl | 0f7ede4 | 2008-06-23 11:23:31 +0000 | [diff] [blame] | 88 | from urllib.request import urlopen |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 89 | |
Georg Brandl | 0f7ede4 | 2008-06-23 11:23:31 +0000 | [diff] [blame] | 90 | with closing(urlopen('http://www.python.org')) as page: |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 91 | for line in page: |
Georg Brandl | 6911e3c | 2007-09-04 07:15:32 +0000 | [diff] [blame] | 92 | print(line) |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 93 | |
| 94 | without needing to explicitly close ``page``. Even if an error occurs, |
| 95 | ``page.close()`` will be called when the :keyword:`with` block is exited. |
| 96 | |
| 97 | |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 98 | .. class:: ContextDecorator() |
| 99 | |
| 100 | A base class that enables a context manager to also be used as a decorator. |
| 101 | |
| 102 | Context managers inheriting from ``ContextDecorator`` have to implement |
| 103 | ``__enter__`` and ``__exit__`` as normal. ``__exit__`` retains its optional |
| 104 | exception handling even when used as a decorator. |
| 105 | |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 106 | ``ContextDecorator`` is used by :func:`contextmanager`, so you get this |
| 107 | functionality automatically. |
| 108 | |
| 109 | Example of ``ContextDecorator``:: |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 110 | |
| 111 | from contextlib import ContextDecorator |
| 112 | |
| 113 | class mycontext(ContextDecorator): |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 114 | def __enter__(self): |
| 115 | print('Starting') |
| 116 | return self |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 117 | |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 118 | def __exit__(self, *exc): |
| 119 | print('Finishing') |
| 120 | return False |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 121 | |
| 122 | >>> @mycontext() |
| 123 | ... def function(): |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 124 | ... print('The bit in the middle') |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 125 | ... |
| 126 | >>> function() |
| 127 | Starting |
| 128 | The bit in the middle |
| 129 | Finishing |
| 130 | |
| 131 | >>> with mycontext(): |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 132 | ... print('The bit in the middle') |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 133 | ... |
| 134 | Starting |
| 135 | The bit in the middle |
| 136 | Finishing |
| 137 | |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 138 | This change is just syntactic sugar for any construct of the following form:: |
| 139 | |
| 140 | def f(): |
| 141 | with cm(): |
| 142 | # Do stuff |
| 143 | |
| 144 | ``ContextDecorator`` lets you instead write:: |
| 145 | |
| 146 | @cm() |
| 147 | def f(): |
| 148 | # Do stuff |
| 149 | |
| 150 | It makes it clear that the ``cm`` applies to the whole function, rather than |
| 151 | just a piece of it (and saving an indentation level is nice, too). |
| 152 | |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 153 | Existing context managers that already have a base class can be extended by |
| 154 | using ``ContextDecorator`` as a mixin class:: |
| 155 | |
| 156 | from contextlib import ContextDecorator |
| 157 | |
| 158 | class mycontext(ContextBaseClass, ContextDecorator): |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 159 | def __enter__(self): |
| 160 | return self |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 161 | |
Georg Brandl | 86e78d1 | 2010-07-18 13:43:32 +0000 | [diff] [blame] | 162 | def __exit__(self, *exc): |
| 163 | return False |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 164 | |
Nick Coghlan | 0ded3e3 | 2011-05-05 23:49:25 +1000 | [diff] [blame] | 165 | .. note:: |
| 166 | As the decorated function must be able to be called multiple times, the |
| 167 | underlying context manager must support use in multiple :keyword:`with` |
| 168 | statements. If this is not the case, then the original construct with the |
| 169 | explicit :keyword:`with` statement inside the function should be used. |
| 170 | |
Michael Foord | b3a8984 | 2010-06-30 12:17:50 +0000 | [diff] [blame] | 171 | .. versionadded:: 3.2 |
| 172 | |
| 173 | |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 174 | .. class:: ExitStack() |
| 175 | |
| 176 | A context manager that is designed to make it easy to programmatically |
| 177 | combine other context managers and cleanup functions, especially those |
| 178 | that are optional or otherwise driven by input data. |
| 179 | |
| 180 | For example, a set of files may easily be handled in a single with |
| 181 | statement as follows:: |
| 182 | |
| 183 | with ExitStack() as stack: |
| 184 | files = [stack.enter_context(open(fname)) for fname in filenames] |
| 185 | # All opened files will automatically be closed at the end of |
| 186 | # the with statement, even if attempts to open files later |
| 187 | # in the list throw an exception |
| 188 | |
| 189 | Each instance maintains a stack of registered callbacks that are called in |
| 190 | reverse order when the instance is closed (either explicitly or implicitly |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 191 | at the end of a :keyword:`with` statement). Note that callbacks are *not* |
| 192 | invoked implicitly when the context stack instance is garbage collected. |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 193 | |
| 194 | This stack model is used so that context managers that acquire their |
| 195 | resources in their ``__init__`` method (such as file objects) can be |
| 196 | handled correctly. |
| 197 | |
| 198 | Since registered callbacks are invoked in the reverse order of |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 199 | registration, this ends up behaving as if multiple nested :keyword:`with` |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 200 | statements had been used with the registered set of callbacks. This even |
| 201 | extends to exception handling - if an inner callback suppresses or replaces |
| 202 | an exception, then outer callbacks will be passed arguments based on that |
| 203 | updated state. |
| 204 | |
| 205 | This is a relatively low level API that takes care of the details of |
| 206 | correctly unwinding the stack of exit callbacks. It provides a suitable |
| 207 | foundation for higher level context managers that manipulate the exit |
| 208 | stack in application specific ways. |
| 209 | |
Nick Coghlan | a497b44 | 2012-05-22 23:02:00 +1000 | [diff] [blame] | 210 | .. versionadded:: 3.3 |
| 211 | |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 212 | .. method:: enter_context(cm) |
| 213 | |
| 214 | Enters a new context manager and adds its :meth:`__exit__` method to |
| 215 | the callback stack. The return value is the result of the context |
| 216 | manager's own :meth:`__enter__` method. |
| 217 | |
| 218 | These context managers may suppress exceptions just as they normally |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 219 | would if used directly as part of a :keyword:`with` statement. |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 220 | |
| 221 | .. method:: push(exit) |
| 222 | |
| 223 | Adds a context manager's :meth:`__exit__` method to the callback stack. |
| 224 | |
| 225 | As ``__enter__`` is *not* invoked, this method can be used to cover |
| 226 | part of an :meth:`__enter__` implementation with a context manager's own |
| 227 | :meth:`__exit__` method. |
| 228 | |
| 229 | If passed an object that is not a context manager, this method assumes |
| 230 | it is a callback with the same signature as a context manager's |
| 231 | :meth:`__exit__` method and adds it directly to the callback stack. |
| 232 | |
| 233 | By returning true values, these callbacks can suppress exceptions the |
| 234 | same way context manager :meth:`__exit__` methods can. |
| 235 | |
| 236 | The passed in object is returned from the function, allowing this |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 237 | method to be used as a function decorator. |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 238 | |
| 239 | .. method:: callback(callback, *args, **kwds) |
| 240 | |
| 241 | Accepts an arbitrary callback function and arguments and adds it to |
| 242 | the callback stack. |
| 243 | |
| 244 | Unlike the other methods, callbacks added this way cannot suppress |
| 245 | exceptions (as they are never passed the exception details). |
| 246 | |
| 247 | The passed in callback is returned from the function, allowing this |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 248 | method to be used as a function decorator. |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 249 | |
| 250 | .. method:: pop_all() |
| 251 | |
| 252 | Transfers the callback stack to a fresh :class:`ExitStack` instance |
| 253 | and returns it. No callbacks are invoked by this operation - instead, |
| 254 | they will now be invoked when the new stack is closed (either |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 255 | explicitly or implicitly at the end of a :keyword:`with` statement). |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 256 | |
| 257 | For example, a group of files can be opened as an "all or nothing" |
| 258 | operation as follows:: |
| 259 | |
| 260 | with ExitStack() as stack: |
| 261 | files = [stack.enter_context(open(fname)) for fname in filenames] |
| 262 | close_files = stack.pop_all().close |
| 263 | # If opening any file fails, all previously opened files will be |
| 264 | # closed automatically. If all files are opened successfully, |
| 265 | # they will remain open even after the with statement ends. |
| 266 | # close_files() can then be invoked explicitly to close them all |
| 267 | |
| 268 | .. method:: close() |
| 269 | |
| 270 | Immediately unwinds the callback stack, invoking callbacks in the |
| 271 | reverse order of registration. For any context managers and exit |
| 272 | callbacks registered, the arguments passed in will indicate that no |
| 273 | exception occurred. |
| 274 | |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 275 | |
| 276 | Examples and Recipes |
| 277 | -------------------- |
| 278 | |
| 279 | This section describes some examples and recipes for making effective use of |
| 280 | the tools provided by :mod:`contextlib`. |
| 281 | |
| 282 | |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 283 | Supporting a variable number of context managers |
| 284 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 285 | |
| 286 | The primary use case for :class:`ExitStack` is the one given in the class |
| 287 | documentation: supporting a variable number of context managers and other |
| 288 | cleanup operations in a single :keyword:`with` statement. The variability |
| 289 | may come from the number of context managers needed being driven by user |
| 290 | input (such as opening a user specified collection of files), or from |
| 291 | some of the context managers being optional:: |
| 292 | |
| 293 | with ExitStack() as stack: |
| 294 | for resource in resources: |
| 295 | stack.enter_context(resource) |
| 296 | if need_special resource: |
| 297 | special = acquire_special_resource() |
| 298 | stack.callback(release_special_resource, special) |
| 299 | # Perform operations that use the acquired resources |
| 300 | |
| 301 | As shown, :class:`ExitStack` also makes it quite easy to use :keyword:`with` |
| 302 | statements to manage arbitrary resources that don't natively support the |
| 303 | context management protocol. |
| 304 | |
| 305 | |
| 306 | Simplifying support for single optional context managers |
| 307 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 308 | |
| 309 | In the specific case of a single optional context manager, :class:`ExitStack` |
| 310 | instances can be used as a "do nothing" context manager, allowing a context |
Nick Coghlan | b7a455f | 2012-05-31 22:34:59 +1000 | [diff] [blame] | 311 | manager to easily be omitted without affecting the overall structure of |
Nick Coghlan | 2722827 | 2012-05-31 22:17:08 +1000 | [diff] [blame] | 312 | the source code:: |
| 313 | |
| 314 | def debug_trace(details): |
| 315 | if __debug__: |
| 316 | return TraceContext(details) |
| 317 | # Don't do anything special with the context in release mode |
| 318 | return ExitStack() |
| 319 | |
| 320 | with debug_trace(): |
| 321 | # Suite is traced in debug mode, but runs normally otherwise |
| 322 | |
| 323 | |
| 324 | Catching exceptions from ``__enter__`` methods |
| 325 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 326 | |
| 327 | It is occasionally desirable to catch exceptions from an ``__enter__`` |
| 328 | method implementation, *without* inadvertently catching exceptions from |
| 329 | the :keyword:`with` statement body or the context manager's ``__exit__`` |
| 330 | method. By using :class:`ExitStack` the steps in the context management |
| 331 | protocol can be separated slightly in order to allow this:: |
| 332 | |
| 333 | stack = ExitStack() |
| 334 | try: |
| 335 | x = stack.enter_context(cm) |
| 336 | except Exception: |
| 337 | # handle __enter__ exception |
| 338 | else: |
| 339 | with stack: |
| 340 | # Handle normal case |
| 341 | |
| 342 | Actually needing to do this is likely to indicate that the underlying API |
| 343 | should be providing a direct resource management interface for use with |
| 344 | :keyword:`try`/:keyword:`except`/:keyword:`finally` statements, but not |
| 345 | all APIs are well designed in that regard. When a context manager is the |
| 346 | only resource management API provided, then :class:`ExitStack` can make it |
| 347 | easier to handle various situations that can't be handled directly in a |
| 348 | :keyword:`with` statement. |
| 349 | |
| 350 | |
Nick Coghlan | 3267a30 | 2012-05-21 22:54:43 +1000 | [diff] [blame] | 351 | Cleaning up in an ``__enter__`` implementation |
| 352 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 353 | |
| 354 | As noted in the documentation of :meth:`ExitStack.push`, this |
| 355 | method can be useful in cleaning up an already allocated resource if later |
| 356 | steps in the :meth:`__enter__` implementation fail. |
| 357 | |
| 358 | Here's an example of doing this for a context manager that accepts resource |
| 359 | acquisition and release functions, along with an optional validation function, |
| 360 | and maps them to the context management protocol:: |
| 361 | |
| 362 | from contextlib import contextmanager, ExitStack |
| 363 | |
| 364 | class ResourceManager(object): |
| 365 | |
| 366 | def __init__(self, acquire_resource, release_resource, check_resource_ok=None): |
| 367 | self.acquire_resource = acquire_resource |
| 368 | self.release_resource = release_resource |
| 369 | if check_resource_ok is None: |
| 370 | def check_resource_ok(resource): |
| 371 | return True |
| 372 | self.check_resource_ok = check_resource_ok |
| 373 | |
| 374 | @contextmanager |
| 375 | def _cleanup_on_error(self): |
| 376 | with ExitStack() as stack: |
| 377 | stack.push(self) |
| 378 | yield |
| 379 | # The validation check passed and didn't raise an exception |
| 380 | # Accordingly, we want to keep the resource, and pass it |
| 381 | # back to our caller |
| 382 | stack.pop_all() |
| 383 | |
| 384 | def __enter__(self): |
| 385 | resource = self.acquire_resource() |
| 386 | with self._cleanup_on_error(): |
| 387 | if not self.check_resource_ok(resource): |
| 388 | msg = "Failed validation for {!r}" |
| 389 | raise RuntimeError(msg.format(resource)) |
| 390 | return resource |
| 391 | |
| 392 | def __exit__(self, *exc_details): |
| 393 | # We don't need to duplicate any of our resource release logic |
| 394 | self.release_resource() |
| 395 | |
| 396 | |
| 397 | Replacing any use of ``try-finally`` and flag variables |
| 398 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 399 | |
| 400 | A pattern you will sometimes see is a ``try-finally`` statement with a flag |
| 401 | variable to indicate whether or not the body of the ``finally`` clause should |
| 402 | be executed. In its simplest form (that can't already be handled just by |
| 403 | using an ``except`` clause instead), it looks something like this:: |
| 404 | |
| 405 | cleanup_needed = True |
| 406 | try: |
| 407 | result = perform_operation() |
| 408 | if result: |
| 409 | cleanup_needed = False |
| 410 | finally: |
| 411 | if cleanup_needed: |
| 412 | cleanup_resources() |
| 413 | |
| 414 | As with any ``try`` statement based code, this can cause problems for |
| 415 | development and review, because the setup code and the cleanup code can end |
| 416 | up being separated by arbitrarily long sections of code. |
| 417 | |
| 418 | :class:`ExitStack` makes it possible to instead register a callback for |
| 419 | execution at the end of a ``with`` statement, and then later decide to skip |
| 420 | executing that callback:: |
| 421 | |
| 422 | from contextlib import ExitStack |
| 423 | |
| 424 | with ExitStack() as stack: |
| 425 | stack.callback(cleanup_resources) |
| 426 | result = perform_operation() |
| 427 | if result: |
| 428 | stack.pop_all() |
| 429 | |
| 430 | This allows the intended cleanup up behaviour to be made explicit up front, |
| 431 | rather than requiring a separate flag variable. |
| 432 | |
| 433 | If a particular application uses this pattern a lot, it can be simplified |
| 434 | even further by means of a small helper class:: |
| 435 | |
| 436 | from contextlib import ExitStack |
| 437 | |
| 438 | class Callback(ExitStack): |
| 439 | def __init__(self, callback, *args, **kwds): |
| 440 | super(Callback, self).__init__() |
| 441 | self.callback(callback, *args, **kwds) |
| 442 | |
| 443 | def cancel(self): |
| 444 | self.pop_all() |
| 445 | |
| 446 | with Callback(cleanup_resources) as cb: |
| 447 | result = perform_operation() |
| 448 | if result: |
| 449 | cb.cancel() |
| 450 | |
| 451 | If the resource cleanup isn't already neatly bundled into a standalone |
| 452 | function, then it is still possible to use the decorator form of |
| 453 | :meth:`ExitStack.callback` to declare the resource cleanup in |
| 454 | advance:: |
| 455 | |
| 456 | from contextlib import ExitStack |
| 457 | |
| 458 | with ExitStack() as stack: |
| 459 | @stack.callback |
| 460 | def cleanup_resources(): |
| 461 | ... |
| 462 | result = perform_operation() |
| 463 | if result: |
| 464 | stack.pop_all() |
| 465 | |
| 466 | Due to the way the decorator protocol works, a callback function |
| 467 | declared this way cannot take any parameters. Instead, any resources to |
| 468 | be released must be accessed as closure variables |
| 469 | |
| 470 | |
| 471 | Using a context manager as a function decorator |
| 472 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 473 | |
| 474 | :class:`ContextDecorator` makes it possible to use a context manager in |
| 475 | both an ordinary ``with`` statement and also as a function decorator. |
| 476 | |
| 477 | For example, it is sometimes useful to wrap functions or groups of statements |
| 478 | with a logger that can track the time of entry and time of exit. Rather than |
| 479 | writing both a function decorator and a context manager for the task, |
| 480 | inheriting from :class:`ContextDecorator` provides both capabilities in a |
| 481 | single definition:: |
| 482 | |
| 483 | from contextlib import ContextDecorator |
| 484 | import logging |
| 485 | |
| 486 | logging.basicConfig(level=logging.INFO) |
| 487 | |
| 488 | class track_entry_and_exit(ContextDecorator): |
| 489 | def __init__(self, name): |
| 490 | self.name = name |
| 491 | |
| 492 | def __enter__(self): |
| 493 | logging.info('Entering: {}'.format(name)) |
| 494 | |
| 495 | def __exit__(self, exc_type, exc, exc_tb): |
| 496 | logging.info('Exiting: {}'.format(name)) |
| 497 | |
| 498 | Instances of this class can be used as both a context manager:: |
| 499 | |
| 500 | with track_entry_and_exit('widget loader'): |
| 501 | print('Some time consuming activity goes here') |
| 502 | load_widget() |
| 503 | |
| 504 | And also as a function decorator:: |
| 505 | |
| 506 | @track_entry_and_exit('widget loader') |
| 507 | def activity(): |
| 508 | print('Some time consuming activity goes here') |
| 509 | load_widget() |
| 510 | |
| 511 | Note that there is one additional limitation when using context managers |
| 512 | as function decorators: there's no way to access the return value of |
| 513 | :meth:`__enter__`. If that value is needed, then it is still necessary to use |
| 514 | an explicit ``with`` statement. |
| 515 | |
Georg Brandl | 116aa62 | 2007-08-15 14:28:22 +0000 | [diff] [blame] | 516 | .. seealso:: |
| 517 | |
| 518 | :pep:`0343` - The "with" statement |
| 519 | The specification, background, and examples for the Python :keyword:`with` |
| 520 | statement. |
| 521 | |