Transcription
Вы когда-нибудь видели символ @ над определением функции раньше? Это синтаксис декоратора в действии. Декоратор, такой как timer deck здесь, сам по себе является функцией. Назначение этой функции — декорировать или улучшать базовую функцию, которую мы передаем в качестве аргумента, и возвращать улучшенную функцию. Написав @timer deck над определением brew t, мы говорим Python, что базовая функция brew t должна быть улучшена декоратором timer deck перед ее использованием. Получив базовую функцию в качестве входных данных, декоратор объединяет ее с дополнительными функциями, не изменяя исходный код базовой функции. После добавления этих новых функций декоратор возвращает улучшенную версию функции. Именно эту улучшенную версию функции Python будет использовать всякий раз, когда вызывается brew tea. В двух словах, вот как работают декораторы. Хорошо, но в чем смысл? Зачем использовать декораторы для добавления дополнительного кода, когда мы можем просто включить дополнительные операции в исходное определение функции? Давайте посмотрим, почему, немного развив наш пример заваривания чая. Мы начнем с написания тела функции brew tea. Функция просто печатает "brewing tea", делает паузу на секунду, а затем печатает "tea is ready". Конечно, выполнение этой функции займет около 1 секунды. Но предположим, мы хотим знать точное время выполнения. Для этого мы могли бы добавить строки кода для записи времени начала вызова функции, времени окончания вызова функции и вывода разницы между ними. Запустив код, функция действительно рассчитывает время выполнения. Но у этого подхода есть проблемы. Во-первых, функция brew tea нарушает принцип единственной ответственности, выполняя две разные задачи: заваривание чая и измерение времени процесса. В программировании функции должны сосредоточиться на одной четко определенной ответственности, чтобы сделать код повторно используемым. В этом примере, объединив заваривание и измерение времени, мы не можем легко повторно использовать логику измерения времени в другой функции. Например, предположим, у нас также есть функция приготовления матча, и мы хотим измерить время ее выполнения. Мы могли бы сделать это, переписав код измерения времени из функции brew tea, но дублирование кода не является идеальным, поскольку это делает нашу кодовую базу повторяющейся и труднее поддерживаемой. Декораторы предлагают отличное решение этих проблем. Давайте посмотрим, как. Для простоты мы сначала декорируем функцию brew tea. Мы вернем make matcha позже. Поскольку декораторы — это просто функции, для создания одного мы начнем с ключевого слова def. В этом примере назначение нашего декоратора — измерять время выполнения функции. Поэтому мы назовем его timer deck. Помните, что декораторы принимают базовую функцию в качестве входных данных. Поэтому мы определим параметр под названием base function для представления этой входной функции. Это завершает заголовок декоратора. Перейдем к телу. Поскольку цель декоратора — создать [музыка] улучшенную функцию и вернуть ее. Давайте определим эту улучшенную функцию. Улучшенная функция будет использовать базовую функцию в качестве своей основы. Поэтому она должна включать вызов base function. Оттуда мы можем добавить код, который хотим выполнить до и после вызова базовой функции. Чтобы измерить время выполнения базовой функции, мы запишем время начала до вызова, время окончания после вызова, а затем выведем разницу. И это все. Мы создали декоратор, который принимает базовую функцию в качестве входных данных и добавляет операции для измерения ее времени выполнения. Последний шаг — сказать нашему декоратору вернуть улучшенную функцию. Обратите внимание, что это оператор return является частью декоратора, а не частью улучшенной функции. Отлично. Теперь давайте используем наш декоратор таймера для измерения, сколько времени занимает выполнение функции focus brew [музыка] t. Чтобы применить декораторы в Python, у нас есть два варианта. Во-первых, мы можем вызвать декоратор и передать функцию, которую хотим декорировать, в качестве аргумента. Запустив код, мы получаем объект функции. Этот объект функции — это улучшенная функция, которую вернул timer deck. Чтобы вызвать эту функцию позже в программе, мы можем дать ей имя, присвоив ее переменной, скажем, [музыка] deck brew tea. Вызов deck brew tea заварит чай и измерит время выполнения. В отличие от этого, вызов brew tea по-прежнему просто заваривает чай. Обратите внимание, что при передаче базовой функции декоратору мы просто пишем имя функции. Мы не следуем за именем скобками. Добавление скобок немедленно вызовет функцию brew t, чего мы здесь не хотим. Также важно отметить, что при применении декораторов мы можем переприсвоить декорированную функцию исходному имени функции. Этот подход гарантирует, что каждый вызов исходного имени функции brew tea автоматически включает функциональность таймера. Это очень мощно. Используя декоратор, мы добавили функции к brew tea, не изменяя исходный код. Но в то же время применение декораторов таким образом отделяет декорирование от определения функции. Это делает менее очевидным для того, кто читает этот код, или для нас самих в будущем, что базовая функция brew будет улучшена. К счастью, мы можем устранить этот недостаток с помощью синтаксиса декоратора @, который мы упомянули в начале. Написание этого над функцией эквивалентно ручному применению декоратора. Запустив код, мы видим, что вызов brew tea одновременно заваривает чай и измеряет время процесса. Синтаксис @ делает декорирование явной и видимой частью самого определения функции. По этой причине этот синтаксис обычно используется для применения декораторов в Python. Теперь, когда мы увидели, как декораторы помогают нам писать сфокусированные функции, давайте посмотрим, как они также помогают нам повторно использовать код. Для этого мы вернем функцию make matcha, чтобы измерить ее время. Теперь, когда мы определили декоратор таймера, измерение времени выполнения функции make matcha легко. Мы просто пишем @timer deck над заголовком функции и вызываем функцию. Запустив код, мы видим, что вызов make matcha одновременно создает матча и измеряет время процесса. Отлично. Синтаксис @ позволяет нам легко повторно использовать декоратор и четко показывать, какие функции улучшены. Но до сих пор мы декорировали только простые функции, у которых нет параметров. Чтобы декорировать функции, у которых есть параметры, нам нужно сделать немного больше работы. Чтобы дать себе пространство для работы, давайте свернем функцию make matcha, чтобы увидеть, как декорировать функции с параметрами. Мы изменим нашу функцию brew tea, чтобы она принимала два аргумента: тип чая для заваривания и время его настаивания. Мы будем использовать эти значения внутри нашей функции для обновления первого оператора print и функции sleep. Затем мы обновим вызов нашей функции brew tea, чтобы передать тип чая green и время настаивания один в качестве аргументов. Теперь, когда наша функция brew tea принимает аргументы, запуск кода вызывает ошибку. Эта ошибка говорит нам, что функция, которую мы вызвали, принимает ноль аргументов, но мы передали ей два. Подождите, что? Разве мы не определили brew tea так, чтобы она принимала два аргумента? Ну, мы сделали, но помните, когда мы декорируем brew tea декоратором таймера, вызов brew tea фактически вызывает улучшенную функцию, определенную в декораторе, а улучшенная функция в настоящее время не принимает никаких аргументов. Мы могли бы попытаться исправить эту проблему, создав параметры type и steep time в улучшенной функции и передав их в вызов базовой функции. Запустив код, это исправление сработало для вызова brew t, но мы получаем другую ошибку, когда Python пытается выполнить make matcha. Это происходит потому, что мы теперь указали, что улучшенная функция должна принимать ровно два аргумента. Проблема в том, что make matcha не принимает никаких аргументов, и давайте предположим, что мы не хотим, чтобы она их принимала. Можем ли мы сделать наш декоратор достаточно гибким для обеих функций? Ответ — да. Но нам нужно будет изменить заголовок улучшенной функции. Опять же, проблема с текущим заголовком в том, что он указывает ровно два параметра. В Python мы можем ослабить это требование и позволить функции принимать любое количество входных данных, используя *args и **kwargs. Если вы не знакомы с *args и его сестринским синтаксисом, **kws, обязательно сначала посмотрите наше видео, ссылка на которое находится в описании. Чтобы позволить улучшенной функции принимать любое количество позиционных аргументов, мы заменяем явные параметры type и steep time на *args. Написав *args в определении функции, мы говорим Python упаковать все позиционные аргументы в кортеж с именем args. Это означает, что когда вызывается функция brew, аргументы green и one передаются в улучшенную функцию и упаковываются в кортеж args. Чтобы использовать эти аргументы в вызове базовой функции, может возникнуть соблазн просто передать кортеж args. Однако это не сработает, потому что сам кортеж args считается одним аргументом [музыка], а наша базовая функция ожидает два. Вместо этого нам нужно разделить кортеж на несколько аргументов. Мы можем сделать это, применив оператор распаковки * к кортежу args. Хотя синтаксис *args выглядит одинаково как в определении функции, так и в вызове функции, Python делает разные вещи в каждом случае. При использовании в вызове функции оператор * распаковывает кортеж в отдельные позиционные аргументы вместо упаковки позиционных аргументов в кортеж. Поскольку наш кортеж args содержит green и one, эти значения будут распакованы в два отдельных аргумента. Теперь количество аргументов в вызове базовой функции соответствует количеству параметров в brew tea. Что еще лучше, поскольку улучшенная функция принимает гибкое количество аргументов, декоратор таймера теперь также будет работать с функцией make matcha. Поскольку вызов make matcha не передает никаких аргументов, кортеж args пуст. Поэтому, когда приходит время распаковать кортеж в вызове базовой функции, распаковывать нечего. Это хорошо, поскольку make matcha не имеет параметров. Запустив код, обе функции работают как ожидалось. Однако, если мы остановимся здесь, наш декоратор не будет таким гибким, каким мог бы быть. Например, если в вызове функции brew tea мы вместо этого передадим тип и время настаивания как именованные аргументы, наш код снова сломается. Чтобы исправить это, нам нужно изменить улучшенную функцию, чтобы она также принимала именованные аргументы. Мы можем сделать это, добавив **kwargs в качестве параметра. В этом примере **kwargs говорит Python упаковать все именованные аргументы в словарь с именем kwargs. Поэтому, когда вызывается функция brew t, именованные аргументы t type = green и steep time = 1 передаются в улучшенную функцию. В этот момент они все упакованы в словарь kwargs в виде пар ключ-значение. Чтобы использовать эти аргументы, нам нужно передать их в базовую функцию. Как и с args, мы не можем просто передать сам словарь kwargs. нам нужно разделить его на два отдельных аргумента, используя оператор распаковки словаря **. При использовании в вызове функции оператор ** распаковывает словарь [музыка] в отдельные именованные аргументы. Поэтому во время вызова функции brew tea, когда словарь kwargs содержит эти пары ключ-значение, **kwargs разделяет словарь на именованные аргументы type = green и steep time = 1. Эти именованные аргументы соответствуют параметрам, определенным в заголовке brew t. Запустив код, именованные аргументы теперь принимаются. Используя *args и **kwargs, мы сделали наш декоратор таймера более общим и гибким: он может принимать любое количество позиционных или именованных аргументов и передавать их в базовую функцию. На данный момент единственное, что мешает нашему декоратору стать полностью общим и гибким, — это то, как он в настоящее время обрабатывает возвращаемые значения, или, точнее, не обрабатывает их. Например, предположим, мы хотим, чтобы наша функция make matcha рассчитывала и возвращала оптимальное время для употребления матча, которое составляет 30 минут после его приготовления. Мы можем сделать это, сначала импортировав модули datetime и timedelta из библиотеки datetime. Изменим нашу функцию make matcha, чтобы она возвращала строку, которая гласит "drink matcha by now + 30 minutes", и выведем вывод make matcha. Однако, запустив ячейку, мы получаем None вместо времени "drink by". Это происходит потому, что наш декоратор еще не захватывает и не передает возвращаемое значение из базовой функции. Чтобы исправить это, нам нужно изменить наш декоратор, чтобы он захватывал возвращаемое значение из base function в переменную. затем вернуть эту переменную в конце enhanced function. Снова запустив ячейку, мы теперь получаем точное время, к которому мы должны выпить наш матча. И наш декоратор по-прежнему работает с brew tea, даже если она ничего не возвращает. Отлично. Теперь наш декоратор максимально гибок и универсален. Он может декорировать функции с любым количеством позиционных или именованных аргументов благодаря *args и **kwargs. Он также поддерживает функции, которые возвращают значения, не ломаясь при использовании с функциями, которые этого не делают. Если вы хотите попрактиковаться в том, что вы узнали в этом видео, ознакомьтесь с блокнотом, который мы создали. В нем [музыка] есть несколько упражнений для начала. Мы работаем над множеством других обучающих видео по Python, подобных этому, поэтому обязательно подпишитесь, чтобы не пропустить. Если у вас есть какие-либо вопросы или темы, о которых вы хотели бы узнать, дайте нам знать в комментариях ниже. Мы будем рады услышать от вас. Спасибо за просмотр.