Context Managers ================ .. toctree:: :maxdepth: 1 Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` Introduction ------------ Context managers are used to create the environment (context) required by a section of code, and restore the environment to its original state after the section of code terminates. There are two methods defined by a context manager: * ``__enter__()`` - Executed prior to a section of code executing. Returns an object, related to the context that was created. * ``__exit__()`` - Executed after the section of code is done executing. Guaranteed to run regardless of why the code finished executing (ie. normally or due to an exception), therefore, it can sometimes replace the :py:keyword:`finally` block in a :py:keyword:`try` statement. Python supplies some built-in context managers: * The :py:func:`open` function returns a context manager, which ensures any file opened is always closed. * SQL Lite connection objects, which automatically commit or roll-back a transaction if an exception occurs. * The Lock class of the :py:mod:`multiprocessing` package, which ensures the lock is released if an exception occurs. Context manages can be used on their own by invoking their methods. However, it is more common to use the :py:keyword:`with` statement to do this. .. _section_heading-With_Statement: With Statement -------------- The :py:keyword:`with` statement interfaces with the context manager(s) to automatically: * Setup the context required prior to the ``with_suite`` executing * Teardown the context created after ``with_suite`` finishes executing (either naturally or due to an exception). The :py:keyword:`with` statement can be used as follows: .. code-block:: python with `expression0` [as `target0`] (, `expressionN` [as `targetN`])* : `with_suite` * The ``expression`` is evaluated and expected to return a context manager object. * The ``__enter__()`` method of the context manager object is executed and returns a value related to the context. * If ``target`` is present, the value returned by ``__enter__()`` is assigned to it. * The ``with_suite`` is executed. * The ``__exit__()`` method of the context manager object is executed. * If an exception occurs in the ``with_suite``, the ``__exit__()`` function is supplied with it. The ``__exit__()`` method can decide to handle the exception, ignore it, or let it through un-handled. * You can nest context managers in a :py:keyword:`with` statement with additional ``expression`` and ``target`` sections. Let's use a code example to demonstrate the operation. We define a custom context manager that will be vocal about what is executing: .. code-block:: python :linenos: :emphasize-lines: 3, 7 class MyContextMgr(): def __enter__(self): print("Entering context manager") return "Hello World" def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None: print("Found an exception. Ignoring") print("Exiting context manager") return True Now, the code that uses the context manager in a :py:keyword:`with` statement: .. code-block:: python :linenos: with MyContextMgr() as val: print("Executing with_suite") print("Context manager val: {}".format(val)) print("Done with_suite") Running the code produces the following output: .. code-block:: text Entering context manager Executing with_suite Context manager val: Hello World Done with_suite Exiting context manager If an exception was thrown in the ``with_suite``, as in: .. code-block:: python :linenos: :emphasize-lines: 3 with MyContextMgr() as val: print("Executing with_suite") raise Exception print("Context manager val: {}".format(val)) print("Done with_suite") The ``__exit__()`` method is still invoked even if though the ``with_suite`` was terminated early: .. code-block:: text Entering context manager Executing with_suite Found an exception. Ignoring Exiting context manager Going back to the idea of context managers created by :py:func:`open`, they work like the following: .. code-block:: python with open("foo.txt") as fh: for line in fh: print(line) In this example, the ``foo.txt`` file is opened and the file handle that points to it is passed into the ``with_suite`` as ``fh``. Regardless of how the ``with_suite`` terminates, the file will be closed by the context manager. .. _section_heading-Contextlib: contextlib ---------- The :py:mod:`contextlib` package supplies some handy decorators and context manager classes. For example: .. py:function:: suppress(*exceptions) Return a context manager that suppresses any of the specified exceptions if they occur in the body of a with statement and then resumes execution with the first statement following the end of the with statement. An example of its usage is below: .. code-block:: python import os from contextlib import suppress with suppress(FileNotFoundError): os.remove('foo.txt') In the above case, regardless of what happens, no :py:exc:`FileNotFoundError` exception will make it outside the :py:keyword:`with` statement. There are other context managers in that package that you should explore and considering using where possible. .. admonition:: Try it! :class: TryIt Re-write the following :py:keyword:`try` block to use a :py:keyword:`with` statement. .. code-block:: python try: fh = open("results.txt", mode='w') fh.writeline("Success!") finally: if fh is not None: fh.close()