Привет. В этом видео мы с вами поговорим о декораторах, и это очень важная концепция, которую действительно нужно понять, потому что они используются повсеместно практически во всех приложениях на Python'е. Итак, декоратор — это функция, которая принимает функцию и возвращает функцию. Это очень важный момент, пожалуйста, запомните это. Просто функция, принимающая одну функцию, возвращающая другую функцию. Как вы уже знаете, в Python'е функция — это объект первого класса, поэтому их можно возвращать, принимать и производить с ними разные операции. Итак, простейший декоратор просто возвращает функцию и возвращает её же, то есть такой identity декоратор, который ничего не делает. Однако, часто бывает полезно с функцией что-то делать, и мы с вами это сейчас как раз обсудим. Синтаксис применения декоратора в Python'е такой. На самом деле, это просто так называемый синтаксический сахар, и происходит здесь именно это. У нас вызывается декоратор, ему передаётся функция и записывается всё в функцию, которую мы декорируем. Делается это обычно как раз с помощью @. Не нужно этого бояться, происходит здесь довольно простой момент вот такой. Итак, однако, как вы понимаете, довольно скучно просто возвращать саму же функцию, и нет в этом никакого смысла. Часто бывает необходимо вернуть не ту же самую функцию, а какую-то модифицированную функцию или вообще новую функцию совершенно другую. Например, ещё один простой декоратор, мы можем его определить, он принимает функцию, определяет внутри какую-то новую функцию и возвращает её. Таким образом, у нас при применении декоратора, если мы вызовем decorated, не вызовется hello, потому что вернётся, на самом деле, функция new_func, и мы будем вызывать уже функцию new_func. Можем это проверить, если посмотрим на её имя. На самом деле, decorated — это new_func, потому что опять же у нас вот такой вот синтаксис. У нас декоратор вернул другую функцию, и в decorated записалась новая функция new_func. Важно это понимать. Итак, давайте попробуем сразу перейти к практике, чтобы поподробнее разобраться с тем, как это работает. И напишем декоратор, который записывает в лог результаты выполнения функции. Вообще для чего обычно используются декораторы? Чаще всего они используются для того, чтобы модифицировать поведение каких-то функций. Часто бывает необходимо использовать один декоратор, для того чтобы какое-то семейство функций переопределить, модифицировать их поведение. Например, у нас может быть декоратор login_required, который мы можем применять к разным функциям и требовать, чтобы в момент исполнения этой функции человек был залогинен на сайте. Или мы можем опять же логировать какую-то информацию, можем считать какие-то метрики, и написав один декоратор, мы можем модифицировать поведение сразу многих функций. Это очень полезный и мощный концепт, который, собственно, мы с вами сейчас рассмотрим. Итак, у нас декоратор, который просто пишет что-то, что произошло в функции, в файл. Мы вот здесь вот определили наш декоратор, назовём его logger, и вот так вот он будет применяться. И теперь при вызове summator'а мы хотим, чтобы у нас результат выполнения summator'а записывался в лог-файл. Также мы можем этот декоратор применять к любой другой функции. У нас всегда результат выполнения будет записываться в файл вне зависимости от того, какая это функция. Давайте попробуем это сделать. Итак, мы определили наш декоратор, и нам нужно из него очевидно вернуть какую-то новую функцию. Давайте назовём ее wrapped. Обычно функции, которые определены внутри декоратора, называются wrapped, decorated, inner, что-то в этом роде, чтобы понять, что это действительно то новое, чем мы переопределяем изначальную функцию. И у нас функция будут принимать num_list для начала, также как и наш summator, потому что мы заменим наш summator на этот wrapped(num_list). Мы потом вернём его, перезаписав summator. Что же здесь должно происходить? Воспользуемся знакомым вашим концептом замыкания и вызовем нашу функцию, передав ей num_list, и, например, откроем файл и запишем в него результат выполнения. Не забудем вернуть result, потому что мы хотим, чтобы у нас, на самом деле, всё работало. Итак, мы написали наш декоратор. Что здесь происходит? Мы, применяя декоратор, подменяем функцию summator новой функцией wrapped, и именно она уже будет выполняться. Эта функция новая, она принимает num_list, так же как и summator, получает результат работы summator'а, записывает результат в файл и просто возвращается. Давайте попробуем вызвать summator. У нас вернулось 15, потому что сумма, действительно, 15. Давайте попробуем открыть файл log.txt и убедиться, что там действительно что-то записано. Да, у нас действительно в log.txt записано 15. Мы можем здесь написать print, чтоб было понятней. log.txt у нас 15. Отлично. У нас появился декоратор, который мы можем применять совершенно к разным функциям, которые принимают num_list, и они записывают результаты выполнения в файл. Так, однако, часто бывает полезно определить декоратор так, чтобы он мог применяться не только к функциям, которые принимают num_list, а, например, к функциям, которые принимают любое количество аргументов, любое количество параметров. Как вы могли догадаться, нам нужно нашу функцию определить так, чтобы она принимала любое количество аргументов. Мы делаем это с помощью звёздочек и передаём все аргументы, которые нам сюда пришли в нашу исходную функцию. Всё должно работать точно так же. Да, действительно, если мы вызовем summator, например напишем здесь print, и выведем результат выполнения summator'а, у нас summator получил 15 и в файле записалось 15, например, 10. Всё работает. Однако, есть один важный момент, и чтобы понять, чем он неудачен, можно написать, попробовать получить имя summator'а. Оказывается, summator у нас — это wrapped. Как вы могли помнить, у нас функция подменяется, поэтому, на самом деле, summator больше не summator. Часто это бывает неудобно, потому что для каких-то методов интроспекции, для отладки важно помнить, в какой функции именно, например, произошло исключение. Для этого существует встроенный модуль functools с методом wraps, который вам позволяет немного сделать поприятнее, и он подменяет определённые аргументы, docstring'и и названия, для того чтобы у нас summator остался summator'ом, и у нас имя, видите, стало вновь summator. Собственно, с этим логгером у нас всё. У нас появился декоратор, который может декорировать функции и записывать их результат в файл. Давайте, пойдём дальше и напишем более сложный логгер. Так, наша задача будет написать декоратор с параметром, который записывает лог-файл, то есть делает то же самое, однако может принимать в качестве параметра файл, в который нужно записать. Как вы видите, здесь мы записываем всегда log.txt, однако нам хотелось бы использовать logger как-то, чтобы передавать, например, new_log.txt. Что же для этого нам нужно сделать? Давайте попробуем подумать. Итак, у нас есть наш новый декоратор. Про wraps мы написали. Есть наш новый декоратор, который принимает уже не функцию, а принимает filename. Собственно, записывает в этот filename результат выполнения функции. Что должен вернуть наш декоратор? Он должен вернуть декоратор, то есть, на самом деле, у нас можно рассматривать logger не как декоратор, а как просто функцию, которая возвращает декоратор. И возвращает она декоратор, который принимает функцию. Таким образом, мы вызовем logger вначале, у нас вернётся декоратор, и потом этот декоратор уже будет применяться к функции summator. Что у нас происходит внутри декоратора? Всё абсолютно то же самое, у нас есть wrapped или decorated, мы можем назвать, который принимает какое-то количество аргументов, нам не важно, какое. Собственно, наш декоратор потом её возвращает. Отлично. Что же происходит внутри? У нас выполняется наша функция, записывается всё в result, в функцию мы передаём аргументы её. И, обратите внимание, когда мы открываем файл, мы уже используем не log.txt, а filename с верхнего уровня с знакомым вам замыканием, и записываем в filename результаты выполнения функции. Да, и не забудем, конечно, вернуть result, чтобы всё работало. Давайте будем суммировать какое-нибудь другое количество, например, шестёрочку добавим. Так, давайте попробуем запустить. У нас 21, и откроем new_log.txt и проверим, сколько там записалось. Да, у нас в new_log.txt 21, а значит произошло ровно то, что мы хотели. Давайте посмотрим более внимательно на этот пример. Что здесь происходит? У нас применяется декоратор к функции summator. Вызывается функция logger, которой мы передаём filename. Функция logger возвращает декоратор, который потом уже с помощью @ применяется по знакомому вам правилу. Вот этот декоратор. Этот декоратор принимает функцию, в данном случае это summator, и возвращает новую функцию wrapped, которая подменяет наш summator, и потом вызывается именно она. И внутри этой функции вызывается исходная функция, то есть summator, и записывается результат её выполнения в файл. Ничего сложного, и можно написать аналог. Для синтаксического сахара это выглядит как-то так. У нас есть logger, который принимает log.txt, возвращает декоратор, который принимает summator и возвращает новую функцию. На самом деле, происходит что-то в этом роде. Итак, мы с вами рассмотрели, пожалуй, самый сложный пример на декораторы, и часто бывают именно с такими примерами проблемы. Разобраться в том, какой декоратор что принимает, а что возвращает, довольно сложно. Пожалуйста, обратите на это внимание. Это очень важный концепт, который правда очень часто используется. И последний пример на декораторы. Давайте посмотрим, что будет, если применить сразу несколько декораторов. Декораторы можно чейнить, то есть создавать цепочки из декораторов, определяя сразу несколько декораторов один друг за другом. Например, нам нужно модифицировать как-то поведение функции в каких-то нескольких плоскостях, например, мы можем проверять, залогинен ли пользователь и переданы ли какие-то данные, необходимые для того, чтобы происходила дальше транзакция какая-то или операция. Определим два простых декоратора, первый декоратор и второй декоратор, который просто принтит и вызывает функцию. Применим наши декораторы к функции decorated и посмотрим, в каком порядке они вызовутся. У нас вначале вызвался первый декоратор, потом — второй декоратор. Почему так произошло? Чтобы понять почему, можно написать опять же в простом виде без синтаксического сахара и разобраться, что у нас происходит при применении декораторов. Вначале у нас вызывается функция second_decorator, которая возвращает новую функцию wrapped, таким образом, у нас в целом подменяется функция wrap внутри second_dercorator'а. После этого вызывается first_decorator, который принимает функцию полученную из second_decorator'а wrapped и возвращает ещё одну функцию wrapped, заменяя decorated на неё. Таким образом, у нас итоговая функция decorated — это вот эта вот функция, вызывающая функцию из second_decorator'а. Да, обратите внимание, именно вот эта вот функция у нас в итоге становится decorated'ом. Когда у нас вызывается decorated, у нас вначале пишется print, потом вызывается вот эта вот функция, которая на самом деле попала сюда из second_decorator'а, и потом принтится second_decorator. Будьте с этим внимательны. Порядок применения декораторов очень важен, и от него часто зависит поведение. И ещё один пример на чейн декораторов немного другой. Если мы опишем два декоратора bold и italic и попробуем, например, отформатировать текст, окажется, что у нас italic внутри bold'а, хотя, казалось бы, у нас только что декоратор выполнялся сверху вниз, а здесь — снизу вверх. То же самое, чтобы разобраться, что здесь происходит, нужно просто записать это в классическом виде и посмотреть. У нас вначале вызывается функция italic, которая возвращает wrapped, потом вызывается bold, и именно вот функция wrapped из bold'а записывается в hello. Она и будет вызываться. У нас вызывается hello, вызывается функция wrapped, которая внутри себя возвращает вот такой вот объект. Что происходит? Чтобы создать этот объект, нам нужно вызвать функцию вот эту вот. А функция func в данном случае, на самом деле, эта функция из italic'а. И именно поэтому у нас вначале внутри написан тег i, а не b, как вы могли подумать, читая сверху вниз. Опять же будьте внимательны в порядке применения декораторов, это очень важно. Итак, на этом всё. Мы с вами разобрали всё, что хотели, научились применять декораторы, создавать свои новые декораторы, делать декораторы с параметрами, и попробовали декораторы чейнить, то есть применять их один за другим. Пожалуйста, разберитесь с этим концептом, это очень важно. Увидимся в следующем видео.