|
8 | 8 |
|
9 | 9 | :mod:`context_managers` - documentation not written |
10 | 10 | ====================================== |
| 11 | + |
| 12 | +Context managers are awesome |
| 13 | +---------------------------- |
| 14 | + |
| 15 | +I love context managers, and I love the :keyword:`with` keyword. If you've |
| 16 | +never dealt with context managers or :keyword:`with`, `here's a practical guide |
| 17 | +which explains how to use them.`_ You may also read the more official :pep:`PEP |
| 18 | +343 <343>` which introduced these features to the language. |
| 19 | + |
| 20 | +Using :keyword:`with` and context managers in your code contributes a lot to making your code more beautiful and maintainable. Every time you replace a :keyword:`try`\-:keyword:`finally` clause with a :keyword:`with` clause, an angel gets a pair of wings. |
| 21 | + |
| 22 | +Now, you don't *need* any official :class:`ContextManager` class in order to |
| 23 | +use context managers or define them; you just need to define |
| 24 | +:method:`__enter__` and :method:`__exit__` methods in your class, and then you |
| 25 | +can use your class as a context manager. *But*, if you use the |
| 26 | +:class:`ContextManager` class as a base class to your context manager class, |
| 27 | +you could enjoy a few more features that might make your code a bit more |
| 28 | +concise and elegant. |
| 29 | + |
| 30 | +What does :class:`ContextManager` add? |
| 31 | +-------------------------------------- |
| 32 | + |
| 33 | +The :class:`ContextManager` class allows using context managers as decorators |
| 34 | +(in addition to their normal use) and supports writing context managers in a |
| 35 | +new form called :method:`manage_context`. (As well as the original forms). |
| 36 | +First let's import: |
| 37 | + |
| 38 | + >>> from python_toolbox import context_managers |
| 39 | + |
| 40 | +Now let's go over the features one by one. |
| 41 | + |
| 42 | +The :class:`ContextManager` class allows you to **define** context managers in |
| 43 | +new ways and to **use **context managers in new ways. I'll explain both of |
| 44 | +these; let's start with **defining** context managers. |
| 45 | + |
| 46 | +Defining context managers |
| 47 | +------------------------- |
| 48 | + |
| 49 | +There are 3 different ways in which context managers can be defined, and each |
| 50 | +has their own advantages and disadvantages over the others. |
| 51 | + |
| 52 | + - The classic way to define a context manager is to define a class with |
| 53 | + :method:`__enter__` and :method:`__exit__` methods. This is allowed, and if you |
| 54 | + do this you should still inherit from :class:`ContextManager`. Example: |
| 55 | + |
| 56 | + >>> class MyContextManager(context_managers.ContextManager): |
| 57 | + ... def __enter__(self): |
| 58 | + ... pass # preparation |
| 59 | + ... def __exit__(self, type_=None, value=None, traceback=None): |
| 60 | + ... pass # cleanup |
| 61 | + |
| 62 | + - As a decorated generator, like so: |
| 63 | + |
| 64 | + >>> @context_managers.ContextManagerType |
| 65 | + ... def MyContextManager(): |
| 66 | + ... # preparation |
| 67 | + ... try: |
| 68 | + ... yield |
| 69 | + ... finally: |
| 70 | + ... pass # cleanup |
| 71 | + |
| 72 | + |
| 73 | + The advantage of this approach is its brevity, and it may be a good fit for |
| 74 | + relatively simple context managers that don't require defining an actual class. |
| 75 | + This usage is nothing new; it's also available when using the standard |
| 76 | + library's :decorator:`contextlib.contextmanager` decorator. One thing that is |
| 77 | + allowed here that :mod:`contextlib` doesn't allow is to yield the context |
| 78 | + manager itself by doing ``yield context_managers.SelfHook``. |
| 79 | + |
| 80 | + - The third and novel way is by defining a class with a :method:`manage_context` |
| 81 | + method which returns a decorator. Example: |
| 82 | + |
| 83 | + >>> class MyContextManager(ContextManager): |
| 84 | + ... def manage_context(self): |
| 85 | + ... do_some_preparation() |
| 86 | + ... with other_context_manager: |
| 87 | + ... yield self |
| 88 | + |
| 89 | + |
| 90 | +This approach is sometimes cleaner than defining :method:`__enter__` and |
| 91 | +:method:`__exit__`; especially when using another context manager inside |
| 92 | +:method:`manage_context`. In our example we did ``with other_context_manager`` |
| 93 | +in our :method:`manage_context`, which is shorter, more idiomatic and less |
| 94 | +double-underscore-y than the equivalent classic definition: |
| 95 | + |
| 96 | + >>> class MyContextManager(object): |
| 97 | + ... def __enter__(self): |
| 98 | + ... do_some_preparation() |
| 99 | + ... other_context_manager.__enter__() |
| 100 | + ... return self |
| 101 | + ... def __exit__(self, *exc): |
| 102 | + ... return other_context_manager.__exit__(*exc) |
| 103 | + |
| 104 | +Another advantage of the :method:`manage_context` approach over |
| 105 | +:method:`__enter__` and :method:`__exit__` is that it's better at handling |
| 106 | +exceptions, since any exceptions would be raised inside |
| 107 | +:method:`manage_context` where we could :keyword:`except` them, which is much |
| 108 | +more idiomatic than the way :method:`__exit__` handles exceptions, which is by |
| 109 | +receiving their type and returning whether to swallow them or not. |
| 110 | + |
| 111 | +These were the different ways of defining a context manager. Now let's see the different ways of **using** a context manager: |
| 112 | + |
| 113 | +Using context managers |
| 114 | +---------------------- |
| 115 | + |
| 116 | + |
| 117 | +There are 2 different ways in which context managers can be used: |
| 118 | +<ol><li>The plain old honest-to-Guido :keyword:`with` keyword: |
| 119 | +<pre>with MyContextManager() as my_context_manager: |
| 120 | + do_stuff() |
| 121 | +</pre> |
| 122 | +</li> |
| 123 | +<li> |
| 124 | +As a decorator to a function: |
| 125 | +<pre>@MyContextManager() |
| 126 | +def do_stuff(): |
| 127 | + pass # doing stuff |
| 128 | +</pre> |
| 129 | +When the <code>do_stuff</code> function will be called, the context manager will be used. This functionality is also available in the standard library of Python 3.2+ by using <code><a href="http://docs.python.org/dev/library/contextlib.html#contextlib.ContextDecorator">contextlib.ContextDecorator</a></code>, but here it is combined with all the other goodies given by :class:`ContextManager`. Another advantage that :class:`ContextManager` has over <code>ContextDecorator</code> is that it uses <a href="http://pypi.python.org/pypi/decorator">Michele Simionato's excellent decorator module</a> to preserve the decorated function's signature. |
| 130 | +</li> |
| 131 | +</ol>That's it. Inherit all your context managers from :class:`ContextManager` (or decorate your generator functions with <code>ContextManagerType</code>) to enjoy all of these benefits. |
| 132 | +<h2>Source and tests for <code>context_manager</code></h2> |
| 133 | +<a href="https://github.com/cool-RR/GarlicSim/tree/master/garlicsim/garlicsim/general_misc/context_manager.py">Source for <code>context_manager</code>, well-documented</a>. Here are <a href="https://github.com/cool-RR/GarlicSim/tree/master/garlicsim/test_garlicsim/test_general_misc/test_context_manager">the tests</a>. |
| 134 | +Yes, I used a double metaclass in the implementation, i.e. a metaclass which has a metaclass itself. |
| 135 | +There is also a <a href="https://github.com/cool-RR/GarlicSim-for-Python-3.x/tree/master/garlicsim_py3/garlicsim/general_misc/context_manager.py">Python 3 version of <code>context_manager</code></a>, and <a href="https://github.com/cool-RR/GarlicSim-for-Python-3.x/tree/master/garlicsim_py3/test_garlicsim/test_general_misc/test_context_manager">here are its tests</a>. It's available with the <a href="http://pypi.python.org/pypi/garlicsim_py3">Python 3 fork of GarlicSim</a>. |
| 136 | + |
| 137 | +.. here's a practical guide which explains how to use them.: http://effbot.org/zone/python-with-statement.htm |
0 commit comments