Skip to content

Commit 6006150

Browse files
committed
Merge pull request #627 from sirMackk/master
Adds context manager section to structure.rst
2 parents c0dfe51 + 77cf4f4 commit 6006150

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

docs/writing/structure.rst

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,75 @@ expensive function in a table and use them directly instead of recomputing
316316
them when they have already been computed. This is clearly not part
317317
of the function logic.
318318

319+
Context Managers
320+
----------------
321+
322+
A context manager is a Python object that provides extra contextual information
323+
to an action. This extra information takes the form of running a function upon
324+
initiating the context using the ``with`` statement as well as running a function
325+
upon completing all the code inside the ``with`` block. The most well known
326+
example of using a context manager is operating on a file:
327+
328+
.. code-block:: python
329+
330+
with open('file.txt') as f:
331+
contents = f.read()
332+
333+
Anyone familiar with this pattern knows that invoking ``open`` in this fashion
334+
ensures that ``f``'s ``close`` method will be called at some point. This reduces
335+
a developer's cognitive load and makes code easier to read.
336+
337+
There are two easy ways to implement this functionality yourself: using a class
338+
or using a generator. Let's implement the above functionality ourselves, starting
339+
with the class approach:
340+
341+
.. code-block:: python
342+
343+
class CustomOpen(object):
344+
def __init__(self, filename):
345+
self.file = open(filename)
346+
347+
def __enter__(self):
348+
return self.file
349+
350+
def __exit__(self, ctx_type, ctx_value, ctx_traceback):
351+
self.file.close()
352+
353+
with CustomOpen('file') as f:
354+
contents = f.read()
355+
356+
This is just a regular Python object with two extra methods that are used
357+
by the ``with`` statement. CustomOpen is first instantiated and then its
358+
``__enter__`` method is called and whatever ``__enter__`` returns is assigned to
359+
``f`` in the ``as f`` part of the statement. When the contents of the ``with`` block
360+
is finished executing, the ``__exit__`` method is then called.
361+
362+
And now the generator approach using Python's own
363+
`contextlib <https://docs.python.org/2/library/contextlib.html>`_:
364+
365+
.. code-block:: python
366+
367+
from contextlib import contextmanager
368+
369+
@contextmanager
370+
def custom_open(filename):
371+
f = open(filename)
372+
yield f
373+
f.close()
374+
375+
with custom_open('file') as f:
376+
contents = f.read()
377+
378+
This works in exactly the same way as the class example above, albeit it's
379+
more terse. The ``custom_open`` function executes until it reaches the ``yield``
380+
statement. It then gives control back to the ``with`` statement, which assigns
381+
whatever was ``yield``'ed to `f` in the ``as f`` portion.
382+
383+
Since the two approaches appear the same, we should follow the Zen of Python
384+
to decide when to use which. The class approach might be better if there's
385+
a considerable amount of logic to encapsulate. The function approach
386+
might be better for situations where we're dealing with a simple action.
387+
319388
Dynamic typing
320389
--------------
321390

0 commit comments

Comments
 (0)