{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "13c4cdb4",
   "metadata": {},
   "source": [
    "# Декораторы #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fd8abca",
   "metadata": {},
   "source": [
    "На этой лекции мы с вами поговорим о декораторах, и это очень важная концепция, которую действительно нужно понять, потому что они используются повсеместно практически во всех приложениях на Python'е. \n",
    "\n",
    "Итак, декоратор - это функция, которая принимает функцию и возвращает функцию. Это очень важный момент, пожалуйста, запомните это."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "053894bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "def decorator(func):\n",
    "    return func"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7818e41d",
   "metadata": {},
   "source": [
    "Просто функция, принимающая одну функцию, возвращающая другую функцию. Как вы уже знаете, в Python'е функция - это объект первого класса, поэтому их можно возвращать, принимать и производить с ними разные операции.\n",
    "\n",
    "Итак, простейший декоратор просто возвращает функцию и возвращает её же, то есть такой `identity` декоратор, который ничего не делает. Однако, часто бывает полезно с функцией что-то делать, и мы с вами это сейчас как раз обсудим."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9c451b1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "@decorator\n",
    "def decorated():\n",
    "    print(\"Hello!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "497c0bb8",
   "metadata": {},
   "source": [
    "Синтаксис применения декоратора в Python'е такой. На самом деле, это просто так называемый синтаксический сахар, и происходит здесь именно это. У нас вызывается декоратор, ему передаётся функция и записывается всё в функцию, которую мы декорируем."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0c2285bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "decorated = decorator(decorated)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19dc0fd2",
   "metadata": {},
   "source": [
    "Делается это обычно как раз с помощью `@`. Не нужно этого бояться, происходит здесь довольно простой момент вот такой. \n",
    "\n",
    "Итак, однако, как вы понимаете, довольно скучно просто возвращать саму же функцию, и нет в этом никакого смысла. Часто бывает необходимо вернуть не ту же самую функцию, а какую-то модифицированную функцию или вообще новую функцию совершенно другую.\n",
    "\n",
    "Например, ещё один простой декоратор, мы можем его определить, он принимает функцию, определяет внутри какую-то новую функцию и возвращает её. Таким образом, у нас при применении декоратора, если мы вызовем `decorated`, не вызовется `hello`, потому что вернётся, на самом деле, функция `new_func`, и мы будем вызывать уже функцию `new_func`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "21fa4573",
   "metadata": {},
   "outputs": [],
   "source": [
    "def decorator(func):\n",
    "    def new_func():\n",
    "        pass\n",
    "    \n",
    "    return new_func\n",
    "\n",
    "@decorator\n",
    "def decorated():\n",
    "    print(\"Hello!\")\n",
    "\n",
    "decorated()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "303da690",
   "metadata": {},
   "source": [
    "Можем это проверить, если посмотрим на её имя. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "96066a0b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "new_func\n"
     ]
    }
   ],
   "source": [
    "print(decorated.__name__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "293001dc",
   "metadata": {},
   "source": [
    "На самом деле, `decorated` — это `new_func`, потому что опять же у нас вот такой вот синтаксис. У нас декоратор вернул другую функцию, и в `decorated` записалась новая функция `new_func`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa2fd676",
   "metadata": {},
   "source": [
    "Важно это понимать. Итак, давайте попробуем сразу перейти к практике, чтобы поподробнее разобраться с тем, как это работает. \n",
    "\n",
    "И напишем декоратор, который записывает в лог результаты выполнения функции. Вообще для чего обычно используются декораторы? Чаще всего они используются для того, чтобы модифицировать поведение каких-то функций. Часто бывает необходимо использовать один декоратор, для того чтобы какое-то семейство функций переопределить, модифицировать их поведение. \n",
    "\n",
    "Например, у нас может быть декоратор `login_required`, который мы можем применять к разным функциям и требовать, чтобы в момент исполнения этой функции человек был залогинен на сайте. Или мы можем опять же логировать какую-то информацию, можем считать какие-то метрики, и написав один декоратор, мы можем модифицировать поведение сразу многих функций. Это очень полезный и мощный концепт, который, собственно, мы с вами сейчас рассмотрим.\n",
    "\n",
    "Итак, у нас декоратор, который просто пишет что-то, что произошло в функции, в файл. Мы вот здесь вот определили наш декоратор, назовём его `logger`, и вот так вот он будет применяться. И теперь при вызове `summator`'а мы хотим, чтобы у нас результат выполнения `summator`'а записывался в лог-файл. Также мы можем этот декоратор применять к любой другой функции. У нас всегда результат выполнения будет записываться в файл вне зависимости от того, какая это функция."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "24f785fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(func):\n",
    "    ...\n",
    "\n",
    "@logger\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "# summator([1, 2, 3, 4])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7411ba1d",
   "metadata": {},
   "source": [
    "Давайте попробуем это сделать. Итак, мы определили наш декоратор, и нам нужно из него очевидно вернуть какую-то новую функцию. Давайте назовём ее `wrapped`. Обычно функции, которые определены внутри декоратора, называются `wrapped`, `decorated`, `inner`, что-то в этом роде, чтобы понять, что это действительно то новое, чем мы переопределяем изначальную функцию.\n",
    "\n",
    "И у нас функция будут принимать `num_list` для начала, также как и наш `summator`, потому что мы заменим наш `summator` на этот `wrapped(num_list)`. Мы потом вернём его, перезаписав `summator`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "33aba2ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(func):\n",
    "    def wrapped(num_list):\n",
    "        pass\n",
    "    \n",
    "    return wrapped"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71c217c5",
   "metadata": {},
   "source": [
    "Что же здесь должно происходить? Воспользуемся знакомым вашим концептом замыкания и вызовем нашу функцию, передав ей `num_list`, и, например, откроем файл и запишем в него результат выполнения. Не забудем вернуть `result`, потому что мы хотим, чтобы у нас, на самом деле, всё работало."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "596da402",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(func):\n",
    "    def wrapped(num_list):\n",
    "        result = func(num_list)\n",
    "        \n",
    "        with open(\"log.txt\", \"w\") as f:\n",
    "            f.write(str(result))\n",
    "            \n",
    "        return result\n",
    "    \n",
    "    return wrapped"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05bae755",
   "metadata": {},
   "source": [
    "Итак, мы написали наш декоратор. Что здесь происходит? Мы, применяя декоратор, подменяем функцию `summator` новой функцией `wrapped`, и именно она уже будет выполняться. Эта функция новая, она принимает `num_list`, так же как и `summator`, получает результат работы `summator`'а, записывает результат в файл и просто возвращается. Давайте попробуем вызвать summator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "d9bb65eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@logger\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "summator([1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a881941",
   "metadata": {},
   "source": [
    "У нас вернулось 15, потому что сумма, действительно, 15. Давайте попробуем открыть файл log.txt и убедиться, что там действительно что-то записано."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "d10ea350",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "15\n"
     ]
    }
   ],
   "source": [
    "with open(\"log.txt\", \"r\") as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73ca8968",
   "metadata": {},
   "source": [
    "Да, у нас действительно в `log.txt` записано 15. Отлично. У нас появился декоратор, который мы можем применять совершенно к разным функциям, которые принимают `num_list`, и они записывают результаты выполнения в файл.\n",
    "\n",
    "Так, однако, часто бывает полезно определить декоратор так, чтобы он мог применяться не только к функциям, которые принимают `num_list`, а, например, к функциям, которые принимают любое количество аргументов, любое количество параметров. Как вы могли догадаться, нам нужно нашу функцию определить так, чтобы она принимала любое количество аргументов. Мы делаем это с помощью звёздочек и передаём все аргументы, которые нам сюда пришли в нашу исходную функцию. Всё должно работать точно так же."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b2bed53b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(func):\n",
    "    def wrapped(*args, **kwargs):\n",
    "        result = func(*args, **kwargs)\n",
    "        \n",
    "        with open(\"log.txt\", \"w\") as f:\n",
    "            f.write(str(result))\n",
    "            \n",
    "        return result\n",
    "    \n",
    "    return wrapped"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "046731ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@logger\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "summator([1, 2, 3, 4])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "4b3feec2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n"
     ]
    }
   ],
   "source": [
    "with open(\"log.txt\", \"r\") as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbfe35dd",
   "metadata": {},
   "source": [
    "Да, действительно, если мы вызовем `summator` и выведем результат выполнения summator'а, у нас `summator` получил 10 и в файле записалось 10. Всё работает."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "874a38cb",
   "metadata": {},
   "source": [
    "Однако, есть один важный момент, и чтобы понять, чем он неудачен, можно написать, попробовать получить имя `summator`'а. Оказывается, `summator` у нас — это `wrapped`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "9360cc5f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "wrapped\n"
     ]
    }
   ],
   "source": [
    "print(summator.__name__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66eff2ab",
   "metadata": {},
   "source": [
    "Как вы могли помнить, у нас функция подменяется, поэтому, на самом деле, `summator` больше не `summator`. Часто это бывает неудобно, потому что для каких-то методов интроспекции, для отладки важно помнить, в какой функции именно, например, произошло исключение. Для этого существует встроенный модуль `functools` с методом `wraps`, который вам позволяет немного сделать поприятнее, и он подменяет определённые аргументы, `docstring`'и и названия, для того чтобы у нас `summator` остался `summator`'ом, и у нас имя, видите, стало вновь `summator`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "88f873c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Summator: 21\n",
      "summator\n"
     ]
    }
   ],
   "source": [
    "import functools\n",
    "\n",
    "def logger(func):\n",
    "    @functools.wraps(func)\n",
    "    def wrapped(*args, **kwargs):\n",
    "        result = func(*args, **kwargs)\n",
    "        \n",
    "        with open(\"log.txt\", \"w\") as f:\n",
    "            f.write(str(result))\n",
    "        return result\n",
    "    \n",
    "    return wrapped\n",
    "\n",
    "\n",
    "@logger\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "print(f\"Summator: {summator([1, 2, 3, 4, 5, 6])}\")\n",
    "print(summator.__name__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "010a833e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "21\n"
     ]
    }
   ],
   "source": [
    "with open(\"log.txt\", \"r\") as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be0170f8",
   "metadata": {},
   "source": [
    "Собственно, с этим логгером у нас всё. У нас появился декоратор, который может декорировать функции и записывать их результат в файл.\n",
    "\n",
    "Давайте, пойдём дальше и напишем более сложный логгер. Так, наша задача будет написать декоратор с параметром, который записывает лог-файл, то есть делает то же самое, однако может принимать в качестве параметра файл, в который нужно записать.\n",
    "\n",
    "Что же для этого нам нужно сделать? \n",
    "\n",
    "Давайте попробуем подумать. Есть наш новый декоратор, который принимает уже не функцию, а принимает `filename`. Собственно, записывает в этот `filename` результат выполнения функции.\n",
    "\n",
    "Что должен вернуть наш декоратор? Он должен вернуть декоратор, то есть, на самом деле, у нас можно рассматривать `logger` не как декоратор, а как просто функцию, которая возвращает декоратор. И возвращает она декоратор, который принимает функцию. Таким образом, мы вызовем `logger` вначале, у нас вернётся декоратор, и потом этот декоратор уже будет применяться к функции `summator`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "e0bf0f15",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(filename):\n",
    "    def decorator(func):\n",
    "        pass\n",
    "    \n",
    "    return decorator\n",
    "\n",
    "@logger(\"new_log.txt\")\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "# summator([1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9c12801",
   "metadata": {},
   "source": [
    "Что у нас происходит внутри декоратора? Всё абсолютно то же самое, у нас есть wrapped или decorated, мы можем назвать, который принимает какое-то количество аргументов, нам не важно, какое. Собственно, наш декоратор потом её возвращает."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "e436af25",
   "metadata": {},
   "outputs": [],
   "source": [
    "def logger(filename):\n",
    "    def decorator(func):\n",
    "        def wrapped(*args, **kwargs):\n",
    "            pass\n",
    "        \n",
    "        return wrapped\n",
    "    \n",
    "    return decorator\n",
    "\n",
    "@logger(\"new_log.txt\")\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "# summator([1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "241d2f1f",
   "metadata": {},
   "source": [
    "Отлично. Что же происходит внутри? У нас выполняется наша функция, записывается всё в `result`, в функцию мы передаём аргументы её. И, обратите внимание, когда мы открываем файл, мы уже используем не `log.txt`, а `filename` с верхнего уровня с знакомым вам замыканием, и записываем в `filename` результаты выполнения функции. Да, и не забудем, конечно, вернуть `result`, чтобы всё работало."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "7c793e93",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "21"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def logger(filename):\n",
    "    def decorator(func):\n",
    "        def wrapped(*args, **kwargs):\n",
    "            result = func(*args, **kwargs)\n",
    "        \n",
    "            with open(filename, \"w\") as f:\n",
    "                f.write(str(result))\n",
    "                \n",
    "            return result\n",
    "        \n",
    "        return wrapped\n",
    "    \n",
    "    return decorator\n",
    "\n",
    "@logger(\"new_log.txt\")\n",
    "def summator(num_list):\n",
    "    return sum(num_list)\n",
    "\n",
    "summator([1, 2, 3, 4, 5, 6])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "622ec4a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "21\n"
     ]
    }
   ],
   "source": [
    "with open(\"new_log.txt\", \"r\") as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39ff16bd",
   "metadata": {},
   "source": [
    "Так, давайте попробуем запустить. У нас 21, и откроем `new_log.txt` и проверим, сколько там записалось. Да, у нас в `new_log.txt` 21, а значит произошло ровно то, что мы хотели. Давайте посмотрим более внимательно на этот пример. Что здесь происходит? У нас применяется декоратор к функции `summator`. Вызывается функция `logger`, которой мы передаём `filename`. Функция `logger` возвращает декоратор, который потом уже с помощью `@` применяется по знакомому вам правилу. Этот декоратор принимает функцию, в данном случае это `summator`, и возвращает новую функцию `wrapped`, которая подменяет наш `summator`, и потом вызывается именно она. И внутри этой функции вызывается исходная функция, то есть `summator`, и записывается результат её выполнения в файл. Ничего сложного, и можно написать аналог. Для синтаксического сахара это выглядит как-то так."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "b2f9d4ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "summator = logger(\"log.txt\")(summator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7a339c4",
   "metadata": {},
   "source": [
    "У нас есть `logger`, который принимает `log.txt`, возвращает декоратор, который принимает summator и возвращает новую функцию. На самом деле, происходит что-то в этом роде.\n",
    "\n",
    "Итак, мы с вами рассмотрели, пожалуй, самый сложный пример на декораторы, и часто бывают именно с такими примерами проблемы. Разобраться в том, какой декоратор что принимает, а что возвращает, довольно сложно. Пожалуйста, обратите на это внимание. Это очень важный концепт, который правда очень часто используется."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3a7cea3",
   "metadata": {},
   "source": [
    "И последний пример на декораторы. Давайте посмотрим, что будет, если применить сразу несколько декораторов. Декораторы можно чейнить, то есть создавать цепочки из декораторов, определяя сразу несколько декораторов один друг за другом. Например, нам нужно модифицировать как-то поведение функции в каких-то нескольких плоскостях, например, мы можем проверять, залогинен ли пользователь и переданы ли какие-то данные, необходимые для того, чтобы происходила дальше транзакция какая-то или операция. \n",
    "\n",
    "Определим два простых декоратора, первый декоратор и второй декоратор, который просто принтит и вызывает функцию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "11336dfb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def first_decorator(func):\n",
    "    def wrapped():\n",
    "        print(\"Inside first_decorator product\")\n",
    "        return func()\n",
    "    \n",
    "    return wrapped\n",
    "\n",
    "def second_decorator(func):\n",
    "    def wrapped():\n",
    "        print(\"Inside second_decorator product\")\n",
    "        return func()\n",
    "    \n",
    "    return wrapped"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58cdcbc2",
   "metadata": {},
   "source": [
    "Применим наши декораторы к функции `decorated` и посмотрим, в каком порядке они вызовутся."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "e4d147dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "@first_decorator\n",
    "@second_decorator\n",
    "def decorated():\n",
    "    print(\"Finally called...\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "1a98d952",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Inside first_decorator product\n",
      "Inside second_decorator product\n",
      "Finally called...\n"
     ]
    }
   ],
   "source": [
    "decorated()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0861caa2",
   "metadata": {},
   "source": [
    "У нас вначале вызвался первый декоратор, потом — второй декоратор. Почему так произошло? Чтобы понять почему, можно написать опять же в простом виде без синтаксического сахара и разобраться, что у нас происходит при применении декораторов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "a05eacb4",
   "metadata": {},
   "outputs": [],
   "source": [
    "decorated = first_decorator(second_decorator(decorated))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6542fd87",
   "metadata": {},
   "source": [
    "Вначале у нас вызывается функция `second_decorator`, которая возвращает новую функцию `wrapped`, таким образом, у нас в целом подменяется функция `wrap` внутри `second_dercorator`'а. После этого вызывается `first_decorator`, который принимает функцию полученную из `second_decorator`'а `wrapped` и возвращает ещё одну функцию `wrapped`, заменяя `decorated` на неё. Таким образом, у нас итоговая функция `decorated` — это вот эта вот функция, вызывающая функцию из `second_decorator`'а. Когда у нас вызывается `decorated`, у нас вначале пишется `print`, потом вызывается вот эта вот функция, которая на самом деле попала сюда из `second_decorator`'а, и потом принтится `second_decorator`. \n",
    "\n",
    "Будьте с этим внимательны. Порядок применения декораторов очень важен, и от него часто зависит поведение.\n",
    "\n",
    "И ещё один пример на чейн декораторов немного другой. Если мы опишем два декоратора `bold` и `italic` и попробуем, например, отформатировать текст, окажется, что у нас `italic` внутри `bold`'а, хотя, казалось бы, у нас только что декоратор выполнялся сверху вниз, а здесь - снизу вверх."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "72d0d6d5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<b><i>hello world</i></b>\n"
     ]
    }
   ],
   "source": [
    "def bold(func):\n",
    "    def wrapped():\n",
    "        return \"<b>\" + func() + \"</b>\"\n",
    "    \n",
    "    return wrapped\n",
    "\n",
    "\n",
    "def italic(func):\n",
    "    def wrapped():\n",
    "        return \"<i>\" + func() + \"</i>\"\n",
    "    \n",
    "    return wrapped\n",
    "\n",
    "@bold\n",
    "@italic\n",
    "def hello():\n",
    "    return \"hello world\"\n",
    "\n",
    "# hello = bold(italic(hello))\n",
    "print(hello())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bab7df7e",
   "metadata": {},
   "source": [
    "То же самое, чтобы разобраться, что здесь происходит, нужно просто записать это в классическом виде и посмотреть. У нас вначале вызывается функция `italic`, которая возвращает `wrapped`, потом вызывается `bold`, и именно вот функция `wrapped` из `bold`'а записывается в `hello`. Она и будет вызываться. У нас вызывается `hello`, вызывается функция `wrapped`, которая внутри себя возвращает вот такой вот объект. \n",
    "\n",
    "Что происходит? Чтобы создать этот объект, нам нужно вызвать функцию вот эту вот. А функция `func` в данном случае, на самом деле, эта функция из `italic`'а. И именно поэтому у нас вначале внутри написан тег `i`, а не `b`, как вы могли подумать, читая сверху вниз.\n",
    "\n",
    "Опять же будьте внимательны в порядке применения декораторов, это очень важно. Итак, на этом всё. Мы с вами разобрали всё, что хотели, научились применять декораторы, создавать свои новые декораторы, делать декораторы с параметрами, и попробовали декораторы чейнить, то есть применять их один за другим. Пожалуйста, разберитесь с этим концептом, это очень важно."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
