{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Menadżery kontekstu\n", "\n", "## Wprowadzenie\n", "\n", "Często spotykany w zarządzaniu zasobami jest następujący idiom:\n", "\n", "```python\n", "do_setup()\n", "try:\n", " do_task()\n", "except SomeError:\n", " handle_the_error()\n", "finally:\n", " do_cleanup()\n", "```\n", "\n", "Wyrażenie *with*\n", "----------------\n", "\n", "Aby uprościć i uodpornić się na błędy programisty, od Pythona 2.5 wzwyż dostępne jest wyrażenie *with*.\n", "\n", "Menedżer kontekstu (*context manager*) jest odpowiedzialny za zarządzanie zasobami wewnątrz bloku kodu.\n", "\n", "Najczęściej tworzy te zasoby na początku bloku, a zwalnia na końcu.\n", "\n", "Na przykład, menadżer kontekstu dla plików upewnia się, że pliki zostały prawidłowo zamknięte po zakończeniu bloku, nawet jeśli zostanie zgłoszony wyjątek.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "with open('myfile.txt', 'wt') as f:\n", " f.write('foo bar')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Odpowiednikiem bloku:\n", "\n", "```python\n", "with VAR = EXPR:\n", " BLOCK\n", "```\n", "\n", "jest zapis:\n", "\n", "```python\n", "VAR = EXPR\n", "VAR.__enter__()\n", "try:\n", " BLOCK\n", "finally:\n", " VAR.__exit__()\n", "```\n", "\n", "## Protokół menadżera kontekstu\n", "\n", "Menedżer kontekstu jest klasą posiadającą dwie metody specjalne:\n", "\n", "* ``__enter__`` - metoda wywoływana na samym początku bloku wewnątrz *with*.\n", "\n", "* ``__exit__`` - metoda jest odpowiednikiem `finally:`, wywoływana po zakończeniu bloku `with`.\n", "\n", "Poniżej przedstawiono przykładowy, prosty menadżer kontekstu:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class Context:\n", " def __init__(self):\n", " print('__init__()')\n", " \n", " def __enter__(self):\n", " print('__enter__()')\n", " return self\n", " \n", " def __exit__(self, exc_type, exc_val, exc_tb):\n", " print('__exit__()')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "__init__()\n", "__enter__()\n", "Doing work inside context\n", "__exit__()\n" ] } ], "source": [ "with Context():\n", " print(\"Doing work inside context\")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Metoda `__enter__`\n", "\n", "Wartością zwracaną przez menadżera kontekstu w funkcji ``__enter__`` może być obiekt, który zostanie przypisany do zmiennej występującej po *as*:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import sys\n", "\n", "def blackhole(*args, **kwargs):\n", " pass\n", "\n", "class SuppressOutput:\n", " def __enter__(self):\n", " print('Context.__enter__()')\n", " self.write, sys.stdout.write = sys.stdout.write, blackhole\n", " return self.write\n", "\n", " def __exit__(self, exc_type, exc_val, exc_tb):\n", " sys.stdout.write = self.write\n", " print('__exit__()')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Context.__enter__()\n", "But this one will be printed\n", "__exit__()\n" ] } ], "source": [ "with SuppressOutput() as stdout_write:\n", " print('That won\\'t be printed')\n", " stdout_write('But this one will be printed\\n')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Metoda `__exit__`\n", "\n", "Do metody ``__exit__`` trafia informacja o wyjątkach, jakie pojawiły się bloku\n", "`with`.\n", "* Jeśli metoda ``__exit__`` zwraca `true`, to wyjątek został\n", "obsłużony przez menadżera kontekstu.\n", "* Jeśli zwrócona zostanie wartość `false`, to wyjątek będzie propagowany dalej." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class Context:\n", " def __enter__(self):\n", " pass\n", " \n", " def __exit__(self, excpt_type, excpt_val, excpt_tb):\n", " print(\"Exception type:\", excpt_type)\n", " print(\"Exception value:\", excpt_val)\n", " print(\"Traceback object:\", excpt_tb)\n", " return True # or False" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception type: None\n", "Exception value: None\n", "Traceback object: None\n" ] } ], "source": [ "with Context():\n", " x = 2" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception type: \n", "Exception value: division by zero\n", "Traceback object: \n" ] } ], "source": [ "with Context():\n", " x = 2 / 0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## contextlib.contextmanager\n", "\n", "W prostych przypadkach zamiast tworzyć klasę, możemy skorzystać z gotowego dekoratora zawartego w module *contextlib*, który konwertuje składnię funkcji do postaci menadżera kontekstu:\n", "\n", "```python\n", "from contextlib import contextmanager\n", "\n", "@contextmanager\n", "def make_context():\n", " try:\n", " prepare_resource()\n", " yield context_object\n", " except RuntimeError as err:\n", " handle_exception_here()\n", " finally:\n", " do_clean_up()\n", "```\n", "\n", "Przykładowy prosty menadżer kontekstu napisany z użyciem ``contextmanager``:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "going in\n", "inside\n", "coming out\n" ] } ], "source": [ "from contextlib import contextmanager\n", "\n", "@contextmanager\n", "def Shouter():\n", " print('going in')\n", " yield\n", " print('coming out')\n", "\n", "with Shouter():\n", " print('inside')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Jeżeli chcemy obsłużyć rzucone przez funkcję wyjątki, możemy to zrobić w następujący sposób:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "@contextmanager\n", "def Shouter():\n", " print('going in')\n", " try:\n", " yield\n", " except Exception:\n", " print('Error!')\n", " else:\n", " print('no error')" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "going in\n", "no error\n" ] } ], "source": [ "with Shouter():\n", " pass" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "going in\n", "Error!\n" ] } ], "source": [ "with Shouter():\n", " print(1/0)" ] } ], "metadata": { "kernelspec": { "display_name": "jb", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }