Декораторы
Декораторы в Python и примеры их практического использования.
Итак, что же это такое? Для того, чтобы понять, как работают декораторы, в первую очередь следует вспомнить, что функции в python являются объектами, соответственно, их можно возвращать из другой функции или передавать в качестве аргумента. Также следует помнить, что функция в python может быть определена и внутри другой функции.
Вспомнив это, можно смело переходить к декораторам. Декораторы — это, по сути, «обёртки», которые дают нам возможность изменить поведение функции, не изменяя её код.
Создадим свой декоратор «вручную»:
Наверное, теперь мы бы хотели, чтобы каждый раз, во время вызова stand_alone_function, вместо неё вызывалась stand_alone_function_decorated. Для этого просто перезапишем stand_alone_function:
Собственно, это и есть декораторы. Вот так можно было записать предыдущий пример, используя синтаксис декораторов:
То есть, декораторы в python — это просто синтаксический сахар для конструкций вида:
При этом, естественно, можно использовать несколько декораторов для одной функции, например так:
Однако, все декораторы, которые мы рассматривали, не имели одного очень важного функционала — передачи аргументов декорируемой функции. Собственно, это тоже несложно сделать.
Декорирование методов
Один из важных фактов, которые следует понимать, заключается в том, что функции и методы в Python — это практически одно и то же, за исключением того, что методы всегда ожидают первым параметром ссылку на сам объект (self). Это значит, что мы можем создавать декораторы для методов точно так же, как и для функций, просто не забывая про self.
Конечно, если мы создаём максимально общий декоратор и хотим, чтобы его можно было применить к любой функции или методу, то можно воспользоваться распаковкой аргументов:
Python is cool, no argument here. 1 2 3 Любят ли Билл, Линус и Стив утконосов? Определенно! ,) <> Мне 28 лет, а ты бы сколько дал?
Декораторы с аргументами
А теперь попробуем написать декоратор, принимающий аргументы:
Теперь перепишем данный код с помощью декораторов:
Вернёмся к аргументам декораторов, ведь, если мы используем функцию, чтобы создавать декораторы "на лету", мы можем передавать ей любые аргументы, верно?
Таким образом, мы можем передавать декоратору любые аргументы, как обычной функции. Мы можем использовать и распаковку через *args и **kwargs в случае необходимости.
Некоторые особенности работы с декораторами
- Декораторы несколько замедляют вызов функции, не забывайте об этом.
- Вы не можете "раздекорировать" функцию. Безусловно, существуют трюки, позволяющие создать декоратор, который можно отсоединить от функции, но это плохая практика. Правильнее будет запомнить, что если функция декорирована — это не отменить.
- Декораторы оборачивают функции, что может затруднить отладку.
Последняя проблема частично решена добавлением в модуле functools функции functools.wraps, копирующей всю информацию об оборачиваемой функции (её имя, из какого она модуля, её документацию и т.п.) в функцию-обёртку.
Забавным фактом является то, что functools.wraps тоже является декоратором.
Декораторы могут быть использованы для расширения возможностей функций из сторонних библиотек (код которых мы не можем изменять), или для упрощения отладки (мы не хотим изменять код, который ещё не устоялся).
Также полезно использовать декораторы для расширения различных функций одним и тем же кодом, без повторного его переписывания каждый раз, например:
wrapper 0.00011799999999997923 арозА упал ан алапу азор А wrapper 0.00017800000000001148 !amanaP :lanac a ,noep a ,stah eros ,raj a,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a , .
Для вставки кода на Python в комментарий заключайте его в теги
Декораторы в Python: понять и полюбить
Декораторы в Python — полезная вещь, но многие новички её не понимают и обходят стороной. Объясняем, что они из себя представляют и как работают.
Декораторы — один из самых полезных инструментов в Python, однако новичкам они могут показаться непонятными. Возможно, вы уже встречались с ними, например, при работе с Flask, но не хотели особо вникать в суть их работы. Эта статья поможет вам понять, чем являются декораторы и как они работают.
Что такое декоратор?
Новичкам декораторы могут показаться неудобными и непонятными, потому что они выходят за рамки «обычного» процедурного программирования как в Си, где вы объявляете функции, содержащие блоки кода, и вызываете их. То же касается и объектно-ориентированного программирования, где вы определяете классы и создаёте на их основе объекты. Декораторы не принадлежат ни одной из этих парадигм и исходят из области функционального программирования. Однако не будем забегать вперёд, разберёмся со всем по порядку.
Декоратор — это функция, которая позволяет обернуть другую функцию для расширения её функциональности без непосредственного изменения её кода. Вот почему декораторы можно рассматривать как практику метапрограммирования, когда программы могут работать с другими программами как со своими данными. Чтобы понять, как это работает, сначала разберёмся в работе функций в Python.
Как работают функции
Все мы знаем, что такое функции, не так ли? Не будьте столь уверены в этом. У функций Python есть определённые аспекты, с которыми мы нечасто имеем дело, и, как следствие, они забываются. Давайте проясним, что такое функции и как они представлены в Python.
Функции как процедуры
С этим аспектом функций мы знакомы лучше всего. Процедура — это именованная последовательность вычислительных шагов. Любую процедуру можно вызвать в любом месте программы, в том числе внутри другой процедуры или даже самой себя. По этой части больше нечего сказать, поэтому переходим к следующему аспекту функций в Python.
Функции как объекты первого класса
В Python всё является объектом, а не только объекты, которые вы создаёте из классов. В этом смысле он (Python) полностью соответствует идеям объектно-ориентированного программирования. Это значит, что в Python всё это — объекты:
- числа;
- строки;
- классы (да, даже классы!);
- функции (то, что нас интересует).
Тот факт, что всё является объектами, открывает перед нами множество возможностей. Мы можем сохранять функции в переменные, передавать их в качестве аргументов и возвращать из других функций. Можно даже определить одну функцию внутри другой. Иными словами, функции — это объекты первого класса. Из определения в Википедии:
Объектами первого класса в контексте конкретного языка программирования называются элементы, с которыми можно делать всё то же, что и с любым другим объектом: передавать как параметр, возвращать из функции и присваивать переменной.
И тут в дело вступает функциональное программирование, а вместе с ним — декораторы.
Функциональное программирование — функции высших порядков
В Python используются некоторые концепции из функциональных языков вроде Haskell и OCaml. Пропустим формальное определение функционального языка и перейдём к двум его характеристикам, свойственным Python:
- функции являются объектами первого класса;
- следовательно, язык поддерживает функции высших порядков.
Функциональному программированию присущи и другие свойства вроде отсутствия побочных эффектов, но мы здесь не за этим. Лучше сконцентрируемся на другом — функциях высших порядков. Что есть функция высшего порядка? Снова обратимся к Википедии:
Функции высших порядков — это такие функции, которые могут принимать в качестве аргументов и возвращать другие функции.
Если вы знакомы с основами высшей математики, то вы уже знаете некоторые математические функции высших порядков порядка вроде дифференциального оператора d/dx. Он принимает на входе функцию и возвращает другую функцию, производную от исходной. Функции высших порядков в программировании работают точно так же — они либо принимают функцию(и) на входе и/или возвращают функцию(и).
Пара примеров
Раз уж мы ознакомились со всеми аспектами функций в Python, давайте продемонстрируем их в коде:
def hello_world(): print('Hello world!')
Здесь мы определили простую функцию. Из фрагмента кода далее вы увидите, что эта функция, как и классы с числами, является объектом в Python:
>>> def hello_world(): . print('Hello world!') . >>> type(hello_world) >>> class Hello: . pass . >>> type(Hello) >>> type(10)
Как вы заметили, функция hello_world принадлежит типу . Это означает, что она является объектом класса function . Кроме того, класс, который мы определили, принадлежит классу type . От этого всего голова может пойти кругом, но чуть поигравшись с функцией type вы со всем разберётесь.
Теперь давайте посмотрим на функции в качестве объектов первого класса.
Мы можем хранить функции в переменных:
>>> hello = hello_world >>> hello() Hello world!
Определять функции внутри других функций:
>>> def wrapper_function(): . def hello_world(): . print('Hello world!') . hello_world() . >>> wrapper_function() Hello world!
Передавать функции в качестве аргументов и возвращать их из других функций:
>>> def higher_order(func): . print('Получена функция <> в качестве аргумента'.format(func)) . func() . return func . >>> higher_order(hello_world) Получена функция в качестве аргумента Hello world!
Из этих примеров должно стать понятно, насколько функции в Python гибкие. С учётом этого можно переходить к обсуждению декораторов.
Как работают декораторы
Повторим определение декоратора:
Декоратор — это функция, которая позволяет обернуть другую функцию для расширения её функциональности без непосредственного изменения её кода.
Раз мы знаем, как работают функции высших порядков, теперь мы можем понять как работают декораторы. Сначала посмотрим на пример декоратора:
def decorator_function(func): def wrapper(): print('Функция-обёртка!') print('Оборачиваемая функция: <>'.format(func)) print('Выполняем обёрнутую функцию. ') func() print('Выходим из обёртки') return wrapper
Здесь decorator_function() является функцией-декоратором. Как вы могли заметить, она является функцией высшего порядка, так как принимает функцию в качестве аргумента, а также возвращает функцию. Внутри decorator_function() мы определили другую функцию, обёртку, так сказать, которая обёртывает функцию-аргумент и затем изменяет её поведение. Декоратор возвращает эту обёртку. Теперь посмотрим на декоратор в действии:
>>> @decorator_function . def hello_world(): . print('Hello world!') . >>> hello_world() Оборачиваемая функция: Выполняем обёрнутую функцию. Hello world! Выходим из обёртки
Магия, не иначе! Просто добавив @decorator_function перед определением функции hello_world() , мы модифицировали её поведение. Однако как вы уже могли догадаться, выражение с @ является всего лишь синтаксическим сахаром для hello_world = decorator_function(hello_world) .
Иными словами, выражение @decorator_function вызывает decorator_function() с hello_world в качестве аргумента и присваивает имени hello_world возвращаемую функцию.
И хотя этот декоратор мог вызвать вау-эффект, он не очень полезный. Давайте взглянем на другие, более полезные (наверное):
def benchmark(func): import time def wrapper(): start = time.time() func() end = time.time() print('[*] Время выполнения: <> секунд.'.format(end-start)) return wrapper @benchmark def fetch_webpage(): import requests webpage = requests.get('https://google.com') fetch_webpage()
Здесь мы создаём декоратор, замеряющий время выполнения функции. Далее мы используем его на функции, которая делает GET-запрос к главной странице Google. Чтобы измерить скорость, мы сначала сохраняем время перед выполнением обёрнутой функции, выполняем её, снова сохраняем текущее время и вычитаем из него начальное.
После выполнения кода получаем примерно такой результат:
[*] Время выполнения: 1.4475083351135254 секунд.
К этому моменту вы, наверное, начали осознавать, насколько полезными могут быть декораторы. Они расширяют возможности функции без редактирования её кода и являются гибким инструментом для изменения чего угодно.
Используем аргументы и возвращаем значения
В приведённых выше примерах декораторы ничего не принимали и не возвращали. Модифицируем наш декоратор для измерения времени выполнения:
def benchmark(func): import time def wrapper(*args, **kwargs): start = time.time() return_value = func(*args, **kwargs) end = time.time() print('[*] Время выполнения: <> секунд.'.format(end-start)) return return_value return wrapper @benchmark def fetch_webpage(url): import requests webpage = requests.get(url) return webpage.text webpage = fetch_webpage('https://google.com') print(webpage)
Вывод после выполнения:
[*] Время выполнения: 1.4475083351135254 секунд.
Как вы видите, аргументы декорируемой функции передаются функции-обёртке, после чего с ними можно делать что угодно. Можно изменять аргументы и затем передавать их декорируемой функции, а можно оставить их как есть или вовсе забыть про них и передать что-нибудь совсем другое. То же касается возвращаемого из декорируемой функции значения, с ним тоже можно делать что угодно.
Звёздный Python: где и как используются * и **
Декораторы с аргументами
Мы также можем создавать декораторы, которые принимают аргументы. Посмотрим на пример:
def benchmark(iters): def actual_decorator(func): import time def wrapper(*args, **kwargs): total = 0 for i in range(iters): start = time.time() return_value = func(*args, **kwargs) end = time.time() total = total + (end-start) print('[*] Среднее время выполнения: <> секунд.'.format(total/iters)) return return_value return wrapper return actual_decorator @benchmark(iters=10) def fetch_webpage(url): import requests webpage = requests.get(url) return webpage.text webpage = fetch_webpage('https://google.com') print(webpage)
Здесь мы модифицировали наш старый декоратор таким образом, чтобы он выполнял декорируемую функцию iters раз, а затем выводил среднее время выполнения. Однако чтобы добиться этого, пришлось воспользоваться природой функций в Python.
Функция benchmark() на первый взгляд может показаться декоратором, но на самом деле таковым не является. Это обычная функция, которая принимает аргумент iters , а затем возвращает декоратор. В свою очередь, он декорирует функцию fetch_webpage() . Поэтому мы использовали не выражение @benchmark , а @benchmark(iters=10) — это означает, что тут вызывается функция benchmark() (функция со скобками после неё обозначает вызов функции), после чего она возвращает сам декоратор.
Да, это может быть действительно сложно уместить в голове, поэтому держите правило:
Декоратор принимает функцию в качестве аргумента и возвращает функцию.
В нашем примере benchmark() не удовлетворяет этому условию, так как она не принимает функцию в качестве аргумента. В то время как функция actual_decorator() , которая возвращается benchmark() , является декоратором.
Объекты-декораторы
Напоследок стоит упомянуть, что не только функции, а любые вызываемые объекты могут быть декоратором. Экземпляры классов/объекты с методом __call__() тоже можно вызывать, поэтому их можно использовать в качестве декораторов. Эту функциональность можно использовать для создания декораторов, хранящих какое-то состояние. Например, вот декоратор для мемоизации:
from collections import deque class Memoized: def __init__(self, cache_size=100): self.cache_size = cache_size self.call_args_queue = deque() self.call_args_to_result = <> def __call__(self, fn): def new_func(*args, **kwargs): memoization_key = self._convert_call_arguments_to_hash(args, kwargs) if memoization_key not in self.call_args_to_result: result = fn(*args, **kwargs) self._update_cache_key_with_value(memoization_key, result) self._evict_cache_if_necessary() return self.call_args_to_result[memoization_key] return new_func def _update_cache_key_with_value(self, key, value): self.call_args_to_result[key] = value self.call_args_queue.append(key) def _evict_cache_if_necessary(self): if len(self.call_args_queue) > self.cache_size: oldest_key = self.call_args_queue.popleft() del self.call_args_to_result[oldest_key] @staticmethod def _convert_call_arguments_to_hash(args, kwargs): return hash(str(args) + str(kwargs)) @Memoized(cache_size=5) def get_not_so_random_number_with_max(max_value): import random return random.random() * max_value
Само собой, этот декоратор нужен в основном в демонстрационных целях, в реальном приложении для подобного кеширования стоит использовать functools.lru_cache.
P.S.
Тут будут перечислены некоторые важные вещи, которые не были затронуты в статье или были затронуты вскользь. Вам может показаться, что они расходятся с тем, что было написано в статье до этого, но на самом деле это не так.
- Декораторы не обязательно должны быть функциями, это может быть любой вызываемый объект.
- Декораторы не обязаны возвращать функции, они могут возвращать что угодно. Но обычно мы хотим, чтобы декоратор вернул объект того же типа, что и декорируемый объект. Пример:>>> def decorator(func). return 'sumit'. >>> @decorator. def hello_world(). print('hello world'). >>> hello_world'sumit'
- Также декораторы могут принимать в качестве аргументов не только функции. Здесь можно почитать об этом подробнее.
- Необходимость в декораторах может быть неочевидной до написания библиотеки. Поэтому, если декораторы кажутся вам бесполезными, посмотрите на них с точки зрения разработчика библиотеки. Хорошим примером является декоратор представления в Flask.
- Также стоит обратить внимание на functools.wraps() — функцию, которая помогает сделать декорируемую функцию похожей на исходную, делая такие вещи, как сохранение doctstring исходной функции.
Заключение
Надеемся, эта статья помогла вам понять, какая «магия» лежит в основе работы декораторов.
Паттерн проектирования «Декоратор» / «Decorator»
Возложить дополнительные обязанности (прозрачные для клиентов) на отдельный объект, а не на класс в целом.
Описание
Для более детального понимания проблемы, рассмотрим конкретную ситуацию. Пусть имеется некоторый объект — «кнопка», принадлежащий классу объектов «Кнопка», на который понадобилось возложить дополнительные обязанности. Под обязанностями, в данном контексте, понимаются какие-либо особенности поведения объекта. В случае с кнопкой, можно рассмотреть поведение объекта при его отображении на экране. При этом, будем считать, дополнительными обязанностями — отображение рамки кнопки, надписи, иконки. Важно понимать, что все эти обязанности должны иметь возможность быть наложенными как одновременно, так и по отдельности. Очевидно, первое, что приходит на ум — порождение классов (механизм наследования). Для данной задачи возможно это и выход — расширить класс «Кнопка» семью (2 3 -1 = 7) различными классами, сочетающими в себе всевозможные комбинации обязанностей. Это классы: «Кнопка_С_Надписью», «Кнопка_С_Рамкой», «Кнопка_С_Иконкой», «Кнопка_С_Надписью_И_Иконкой», «Кнопка_С Рамкой_И_Иконкой», «Кнопка_С_Надписью_И_Рамкой», «Кнопка_С_Надписью_И_Рамкой_И_Иконкой». А если таких обязанностей будет не три, а хотя бы десять, не говоря уже про неудобство работы с подобной структурой. Безусловно, порождение классов в таком случае — заведомо проигрышный вариант. Однако, из этой ситуации есть выход — паттерн «Декоратор».
Паттерн «Декоратор» позволяет динамически добавлять объекту новые обязанности, не прибегая при этом к порождению классов. При этом, работа с подобной структурой является более удобной и гибкой, нежели со множеством классов. Для этого, ссылка на декорируемый объект помещается в другой класс, называемый «Декоратором». Причем, и декоратор и декорируемый объект реализуют один и тот-же интерфейс, что позволяет вкладывать несколько декораторов друг в друга, добавляя тем самым декорируемому объекту любое число новых обязанностей. Декоратор переадресует внешние вызовы декорируему объекту сопровождая их наложением дополнительных обязанностей.
Практическая задача
Используя паттерн «Декоратор», реализуем каркас редактора блок-схем. Будем использовать «Декоратор» для наложения особенностей отрисовки отдельных элементов схем. Рассмотрим два типа блоков — терминальный блок (начало/конец) и блок процессов (описывает одно или несколько действий) и будем их декорировать рамкой и надписью.
Диаграмма классов
Рассмотрим диаграмму классов. AbstractBlock — интерфейс любого блока блок-схемы, имеющий единственный метод — draw(), вызываемый клиентом. Является одновременно и интерфейсом декорируемого объекта и интерфейсом декоратора. TerminatorBlock и ProcessBlock — уточнения абстрактного блока. AbstractBlockDectorator — абстрактный класс декоратора блоков. Обратите внимание, что это именно абстрактный класс, а не интерфейс. Дело в том, что AbstractBlockDecorator, по умолчанию, в методе draw() делегирует соответствующий метод декорируемого объекта. LabelBlockDecorator и BorderBlockDecorator — уточнения декораторов блока, в качестве декоратора меток и рамок. Данные классы переопределяют метод draw() базового класса, добавляя декорируемому объекту новые обязанности (методы drawLabel(), drawBorder()).
Реализация на Python
По поводу реализации, хотелось бы сделать небольшое замечание. Ввиду того, что Python не поддерживает описание интерфейсов, рекомендуется описывать их как классы, но с обязательным напоминанием программисту, что это именно интерфейс а не абстрактный класс. Напоминание можно сделать в виде исключения NotImplementedError().
# -*- coding: cp1251 -*-
class AbstractBlock:
"" " Абстрактный блок
" ""
def draw(self):
raise NotImplementedError();
class TerminatorBlock(AbstractBlock):
"" " Терминальный блок (начало/конец, вход/выход)
" ""
def draw(self):
print "Terminator block drawing . "
class ProcessBlock(AbstractBlock):
"" " Блок - процесс (один или несколько операторов)
" ""
def draw(self):
print "Process block drawing . "
class AbstractBlockDecorator(AbstractBlock):
"" " Абстракный декоратор блоков
" ""
def __init__(self, decoratee):
# _decoratee - ссылка на декорируемый объект
self._decoratee = decoratee
def draw(self):
self._decoratee.draw()
class LabelBlockDecorator(AbstractBlockDecorator):
"" " Декорирует блок текстовой меткой
" ""
def __init__(self, decoratee, label):
self._decoratee = decoratee
self._label = label
def draw(self):
AbstractBlockDecorator.draw(self)
self._drawLabel()
def _drawLabel(self):
print " . drawing label " + self._label
class BorderBlockDecorator(AbstractBlockDecorator):
"" " Декорирует блок специальной рамкой
" ""
def __init__(self, decoratee, borderWidth):
self._decoratee = decoratee
self._borderWidth = borderWidth
def draw(self):
AbstractBlockDecorator.draw(self)
self._drawBorder()
def _drawBorder(self):
print " . drawing border with width " + str(self._borderWidth)
# терминальный блок
tBlock = TerminatorBlock()
# блок - процесс
pBlock = ProcessBlock()
# Применим LabelDecorator к терминальному блоку
labelDecorator = LabelBlockDecorator(tBlock, "Label222" )
# Применим BorderDecorator к терминальному блоку, после применения LabelDecorator
borderDecorator1 = BorderBlockDecorator(labelDecorator, 22)
# Применим BorderDecorator к блоку - процессу
borderDecorator2 = BorderBlockDecorator(pBlock, 22)
labelDecorator.draw()
borderDecorator1.draw()
borderDecorator2.draw()
* This source code was highlighted with Source Code Highlighter .
Кто-то, наконец-то дождался примеров на питоне.
Декораторы в Python: прокачиваем функции
В программировании часто используются функции — мини-программы, которые делают внутри кода что-то своё. Это удобно, когда нужно несколько раз выполнить одно и то же: найти сумму квадратов, посчитать налог с зарплаты для каждого сотрудника или проверить логин и пароль пользователя.
Но бывает так, что иногда от функции нужно что-то ещё, а она этого не умеет. Чтобы умела и чтобы её не пришлось переписывать, используют декораторы. Сейчас объясним.
Как работают обычные функции
Функция в программировании — это код внутри основной программы, у которого есть своё внутреннее имя. Если использовать имя функции как команду, то программа выполнит этот мини-код, а потом продолжит работу дальше. Это делает код проще и чище, избавляет от повторов, ускоряет разработку, тестирование и совместную работу.
В Python есть встроенные функции, например sum для суммирования и random для создания случайного числа. Если программисту нужны какие-то свои функции, он может их добавить. Достаточно описать функцию в коде один раз, а затем вызывать её, когда требуется.
Чтобы сделать функцию в Python, нужно объявить её в коде служебным словом def , задать её имя и указать в скобках аргументы. Например, мы хотим, чтобы после авторизации программа приветствовала пользователя по имени, которое мы передаём в функцию. Для этого пишем такой код:
# функция, которая приветствует пользователя def user_greeting(name): # указываем, что делает функция print("Привет," + name)
В Python функции являются объектами. Это значит, что функцию можно передавать в другую функцию в качестве аргумента. Именно это свойство функций позволяет их декорировать.
Что такое декоратор
Если нам нужно, чтобы функция сделала что-то ещё, но мы не хотим переписывать её код, можно использовать декоратор. Это функция, которая расширяет возможности другой функции.
Декоратор можно сравнить с матрёшкой, которая содержит ещё одну матрёшку — другую функцию:
Например, у нас есть функция say_hi() , которая приветствует пользователя. Нам нужно, чтобы в нерабочее время пользователь получал предупреждение, что база недоступна из-за профилактических работ.
В исходном виде функция say_hi() нам в этом не поможет — в ней не хватает нужных команд. Если мы будем добавлять их в исходную функцию, то программа может сломаться — дополнительные функции нужны только тут, а в других местах исходная функция и так работает хорошо.
Декоратор как раз позволяет сделать так, чтобы приветствие пользователя в нерабочее время сменялось предупреждением.
Как сделать декоратор
Чтобы сделать декоратор для функции, нужно объявить его служебным словом def, задать имя и аргументы, а затем описать инструкцию нужных действий.
В простейшем виде декоратор выглядит так:
# объявляем функцию, которая будет служить декоратором def my_decorator(func): # объявляем, что декоратор дополняет другую функцию какими-то действиями def wrapper(): # указываем, что должно произойти до вызова другой функции print("Что-то выполняется до функции.") # указываем, что после этого должна работать другая функция func() # указываем, что должно произойти после того, как другая функция отработала print("Что-то выполняется после функции.") return wrapper # объявляем функцию, которую декорируем def say_hi(): # указываем, что делает функция print("Привет!") # указываем, что теперь функция декорирована say_hi = my_decorator(say_hi)
Результат работы этого декоратора будет таким:
Попробуем изменить функцию say_hi() , чтобы она умела ещё и предупреждать о профилактических работах с базой.
Для этого нужно, чтобы какая-то одна новая функция проверяла время, а если оно нерабочее, то вторая новая функция выводила бы предупреждение.
# импортируем модуль даты и времени from datetime import datetime # объявляем функцию, которая будет служить декоратором def base_maintenance(func): # объявляем, что декоратор дополняет другую функцию какими-то действиями def wrapper(): # проверяем, что время рабочее if 10
- Сначала первая функция проверяет, сколько сейчас времени.
- Если время больше 10 утра или меньше 7 вечера, срабатывает обычное приветствие пользователя.
- Если время меньше 10 утра или больше 10 вечера, пользователь получает предупреждение, что база недоступна.
При запуске в нерабочее время получаем сообщение:
Получается, мы на основе старой функции получили новые возможности и нам не пришлось её переписывать.
Приведённый выше код получился немного топорным: в нём трижды указано название функции say_hi() . Чтобы сделать код проще, можно использовать символ @:
# вызываем модуль даты и времени from datetime import datetime # объявляем функцию, которая будет служить декоратором def base_maintenance(func): # объявляем, что декоратор дополняет другую функцию какими-то действиями def wrapper(): # проверяем, что время рабочее if 10
Где применяются декораторы
В самых простых случаях декораторы можно использовать для таких операций:
- логирование — если нам нужно замерять время работы функции или программы;
- кэширование — если при работе функции образуются промежуточные результаты и нам нужно запомнить их все;
- ограничение скорости — если нам нужно замедлить работу какой-то функции;
- повторное выполнение — если нам нужно, чтобы какая-то функция отработала два или больше раз;
- контроль доступа — если нам нужно проверить, что пользователь авторизован.
Что может пойти не так
Фактически декоратор заменяет другую функцию, и его внутренняя функция wrapper не принимает никаких аргументов. Если бы мы декорировали не функцию say_hi() , а функцию user_greeting(name) , то аргумент name не был бы передан без дополнительных действий.
Ещё один недостаток декораторов: если к другой функции были прикреплены какие-то метаданные, они будут скрыты. Про то, что такое метаданные, — в другой раз.
Что дальше
А дальше мы попробуем на практике поработать с декораторами и сделаем с ними что-то полезное — например, замерим время работы программы или доработаем свою систему логирования.