Декораторы в python: Python 3 для начинающих и чайников
Введение в декораторы на Python
Декоратор — это шаблон проектирования, который мы можем использовать для добавления новых функциональных возможностей к уже существующей функции без необходимости изменять ее структуру. Декоратор должен вызываться непосредственно перед функцией, которая должна быть расширена. С помощью декораторов вы можете динамически изменять функциональные возможности методов, функции или класса без непосредственного использования наследования. Это хороший, способ расширения функциональности функции, которую не хотите изменить напрямую.
В этой статье мы подробно обсудим декораторы в Python.
Как создавать декораторы
Давайте посмотрим, как декораторы могут быть созданы. В качестве примера мы создадим декоратор, который будет преобразовывать строки в нижний регистр. Для этого нам нужно создать функцию декоратора, и в ней нужно определить функцию оболочку, в нашем случае wrapper. Посмотрите на следующий код:
def lowercase(func): def wrapper(): func_ret = func() change_to_lowercase = func_ret.lower() return change_to_lowercase return wrapper
В приведенном выше сценарии мы создали декоратор с именем lowercase, который принимает функцию в качестве аргумента. Чтобы опробовать нашу функцию lowercase, нам нужно создать новую функцию и затем передать ее этому декоратору. Обратите внимание, что поскольку функции в Python являются first-class, вы можете назначить функцию переменной или рассматривать ее как единое целое. Мы будем использовать этот трюк для вызова функции декоратора:
def hello_function(): return 'HELLO WORLD' decorate = lowercase(hello_function) print(decorate())
Output
hello world
Обратите внимание, что вы можете объединить вышеупомянутые две части кода в один. Мы создали функцию hello_function(), которая возвращает предложение «HELLO WORLD». Затем мы вызвали декоратор и передали имя этой функции в качестве аргумента, присваивая его переменной decorate. После выполнения можно увидеть, что полученное предложение было преобразовано в нижний регистр.
Тем не менее, есть более простой способ применения декораторов в Python. Можно просто добавить символ @ перед именем функции декоратора чуть выше декорируемой функции. Например:
@lowercase def hello_function(): return 'HELLO WORLD' print(hello_function())
Output
hello world
Как применить несколько декораторов к функции
Python позволяет нам применять более одного декоратора к одной функции. Чтобы сделать это правильно, убедитесь, что вы применяете декораторы в том же порядке, в котором вы запускаете их как обычный код. Например, рассмотрим следующий декоратор:
def split_sentence(func): def wrapper(): func_ret = func() output = func_ret.split() return output return wrapper
Здесь мы создали декоратор, который принимает входное предложение и разбивает его на различные части. Декоратору присвоено имя split_sentence. Давайте теперь применим lowercase и split_sentence декораторы.
Чтобы выполнить эти операции в правильном порядке, примените их следующим образом:
@split_sentence @lowercase def hello_function(): return 'HELLO WORLD' print(hello_function())
Output
['hello', 'world']
Наше предложение было разделено на две части и преобразовано в строчные буквы, так как мы применили к hello_function декораторы lowercase и split_sentence.
Передача аргументов функциям декоратора
Декораторы Python также могут перехватывать аргументы, которые передаются декорированным функциям. Аргументы в свою очередь будут переданы декорированной функции во время выполнения. Рассмотрим следующий пример:
def my_decorator(func): def my_wrapper(argument1, argument2): print("The arguments are: {0}, {1}".format(argument1, argument2)) func(argument1, argument2) return my_wrapper @my_decorator def names(firstName, secondName): print("Your first and second names are {0} and {1} respectively".format(firstName, secondName)) print(names("Nicholas", "Samuel"))
Output
The arguments are: Nicholas, Samuel Your first and second names are Nicholas and Samuel respectively
В приведенном выше сценарии декоратор принимает два аргумента :, argument1 и argument2.
Создание декораторов общего назначения
Декораторы общего назначения могут быть применены к любой функции. Такого рода декораторы очень полезны, например, для отладки.
Мы можем определить их, используя аргументы *args и **kwargs. Все позиционные и именные аргументы хранятся в этих двух переменных соответственно. С помощью args и kwargs мы можем передать любое количество аргументов во время вызова функции. Например:
def my_decorator(func): def my_wrapper(*args, **kwargs): print('Positional arguments:', args) print('Keyword arguments:', kwargs) func(*args) return my_wrapper @my_decorator def function_without_arguments(): print("No arguments") function_without_arguments()
Output
Positional arguments: () Keyword arguments: {} No arguments
Как видите, аргументы декоратору не передавались.
Теперь давайте посмотрим, как мы можем передать значения позиционным аргументам:
@my_decorator def function_with_arguments(x, y, z): print(x, y, z) function_with_arguments(5, 15, 25)
Output
Positional arguments: (5, 15, 25) Keyword arguments: {} 5 15 25
Мы передали три позиционных аргумента декоратору. Чтобы передать именные аргументы, мы должны использовать ключевые слова в вызове функции. Вот пример:
@my_decorator def passing_keyword_arguments(): print("Passing keyword arguments") passing_keyword_arguments(firstName="Nicholas", secondName="Samuel")
Output
Positional arguments: () Keyword arguments: {'secondName': 'Samuel', 'firstName': 'Nicholas'} Passing keyword arguments
Два именных аргумента были переданы декоратору.
Как отлаживать декораторы
В этот момент вы, наверное, видели, что мы используем декораторы для обертывания вокруг функции. Обертывание функции это по сути замыкание. Замыкание скрывает исходное имя функции, список ее параметров и строку документации docstring.
Например: если мы попытаемся получить метаданные для декоратора function_with_arguments, мы получим метаданные замыкания (то есть функции обертки). Давайте продемонстрируем это:
function_with_arguments.__name__
Output
'my_wrapper'
Это представляет большую проблему во время отладки. Тем не менее, Python предоставляет декоратор functools.wraps, который может помочь в решении этой проблемы. Он работает путем копирования потерянных метаданных в ваше замыкание .
Теперь давайте продемонстрируем, как это работает:
import functools def lowercase(func): @functools.wraps(func) def my_wrapper(): return func().lower() return my_wrapper
@lowercase def hello_function(): "Saying hello" return 'HELLO WORLD' print(hello_function())
Output
hello world
Поскольку мы использовали functools.wraps в функции-обертке, мы можем проверить метаданные функции на «hello_function»:
hello_function.__name__
Output
'hello_function'
hello_function.__doc__
Output
'Saying hello'
Приведенный выше скрипт ясно показывает, что метаданные теперь ссылаются на функцию, а не на оболочку. Я рекомендую вам всегда использовать functools.wraps каждый раз, когда вы определяете декоратор. Это намного упростит отладку, в случае необходимости.
Заключение
Цель декораторов — динамически изменять функциональные возможности класса, метода или функции без непосредственного использования наследования или изменения исходного кода класса, метода или функции, которую нам нужно декорировать. В этой статье мы увидели, как создавать простые и универсальные декораторы и как передавать аргументы декораторам. Мы также увидели, как отлаживать декораторы во время разработки с помощью модуля functools.
Оригинал: Introduction to Python Decorators
Была ли вам полезна эта статья?
[7 / 5]
Введение в декораторы в Python
Автор: PythonInDepth
В нескольких следующих постах я хочу поговорить о декораторах. Будет базовое определение, мотивация их использовать, всякие хитрости, а еще куча примеров.
Прежде, чем говорить о декораторах, нужно кое-что узнать о функциях в Python. Допустим, у нас есть функция, которая здоровается с Юпи:
def hey_Jupi(): print("Привет, Юпи!")
Функции в Python — это объекты первого класса, ничем не хуже, чем int’ы или словари. Это значит, что:
Функцию можно присвоить переменной:
say_hi = hey_Jupi say_hi() # Привет, Юпи!
Функцию можно вернуть из функции:
def wrapper(func): print("Юпи пришла.") return func hello_Jupi = wrapper(hey_Jupi) # Юпи пришла. hello_Jupi() # Привет, Юпи!
Функцию можно определить внутри другой функции:
def deco(func): def wrapper(): print("Юпи пришла.") func() return wrapper hey_Jupi = deco(hey_Jupi) hey_Jupi() # Юпи пришла. # Привет, Юпи!
Смотрите, что получилось на последнем шаге. На этапе создания deco
никакой код не выполняется — мы заходим в deco
, видим, что здесь определена функция wrapper
и возвращаем ее. Таким образом мы подменяем исходную hey_Jupi
на wrapper
и получаем новое поведение hey_Jupi
, не изменяя ее код!
Это и называется декоратор. Это настолько удобный и мощный инструмент, что в Python для него придумали специальный синтаксический сахар. При условии, что функция deco
у нас уже определена так же, как выше, можно добавить название декоратора с символом @
перед определением функции и получить эквивалентное поведение:
@deco def hey_Jupi(): print("Привет, Юпи!") hey_Jupi() # Юпи пришла. # Привет, Юпи!
Кстати, этот же декоратор можно применить и к любой другой функции:
@deco def take_five(): print("Юпи, дай пять!") take_five() # Юпи пришла. # Юпи, дай пять!
Декораторы круты тем, что позволяют гибко модифицировать поведение функции, применять одну и ту же модификацию к нескольким функциям сразу и даже менять поведение функций, доступа к коду которых у нас нет! Зачем нам декораторы на реальных проектах?
- Декораторы используют в веб фреймворках для проверки авторизации или для разделения групп пользователей. Например, часть методов доступна только авторизованным пользователям, либо пользователям с определенной ролью, а остальные методы — всем. Для этого нужные методы оборачивают в декораторы, которые делают необходимые проверки.
- Декораторы позволяют проверить, что аргументы функции имеют нужный тип и значения. Это можно сделать на входе в функцию, но иногда проверки переносят в функцию-обертку.
- С помощью декоратора можно замерять время выполнения функций.
В следующих постах разберемся, как комбинировать декораторы и передавать в декоратор параметры. Всем пять!
Как применять и декораторы, и документацию в Python / Хабр
(Этот материал — для начинающих пользователей.)
Декораторы в Питоне конфликтуют с функцией документации help — если применить декоратор, вывод функции help изменится. Чтобы увидеть, пишем простой декоратор:
- def deco(fn):
- def z(*args, **kwargs):
- return fn(*args, **kwargs)
- return z
И 2 функции, с и без декоратора:
- def x(c, d):
- """Это X"""
- pass
- @deco
- def y(a, b = 20):
- """Это Y"""
- pass
Теперь проверяем:
Help on function x: >>> help(x) x(c, d) Это X >>> help(y) Help on function z: z(*args, **kwargs)
Вместо y мы видим описание внутренней функции декоратора.
Если вы делаете свой модуль и хотите снова использовать его через некоторое время, то внутренняя документация будет очень полезной — чтобы узнать, как вызвать конкретную функцию, не надо будет читать исходный код модуля. Но если нужны декораторы, придётся искать какое-то решение…
У функций есть свойства func_doc, func_name, которые, в принципе, можно установить принудительно:
- def deco(fn):
- def z(*args, **kwargs):
- return fn(*args, **kwargs)
- z.func_doc = fn.func_doc
- z.func_name = fn.func_name
- return z
- @deco
- def y(a, b = 20):
- """Это Y"""
- pass
>>> help(y) Help on function y: y(*args, **kwargs) # а должно быть (a, b = 20) Это Y</code>
Но список аргументов всё равно остался от внутренней функции. (Ответ на замечание читателя kmike: декоратор functools.wraps делает то же самое с тем же результатом)
Есть 2 решения: «чистое» в виде модуля и «грязное» в виде хака от Алекса Мартелли:
Модуль decorator
- from decorator import decorator
- @decorator
- def deco(fn):
- def z(*args, **kwargs):
- return fn(*args, **kwargs)
- return z
- @deco
- def y(a, b = 20):
- pass
>>> help(y) Help on function y: y(a, b = 20)
Хак Алекса Мартелли
Суть хака в том, чтобы записать все декорируемые функции в массив lookaside, а функцию inspect.getargspec (которая выдаёт данные о функции для help) заменяем другой, которая выдаёт записанные в lookaside данные, либо вызывает исходную getargspec.
- import functools, inspect
- realgas = inspect.getargspec
- lookaside = dict()
- def fakegas(f):
- if f in lookaside:
- return lookaside[f]
- return realgas(f)
- inspect.getargspec = fakegas
- def deco(fn):
- @functools.wraps(fn)
- def z(*args, **kwargs):
- return fn(*args, **kwargs)
- lookaside[z] = realgas(fn) # обратите внимание, что
- return z
- @deco
- def x(a, b=23):
- """Это X"""
- return a + b
help(x) Help on function x in module __main__: x(a, b=23) Some doc for x.
Полезные декораторы — tirinox.ru
Как и обещал, приведу список полезных декораторов. Среди них как стандартные поставляемые вместе с Python, так и декораторы из других библиотек и исходные коды прочих интересных декораторов.
Начнем с самых известных.
Свойства @property
Декоратор @property
облегчает создание свойств в классах Python. Свойства выглядят как обычные атрибуты (поля) класса, но при их чтении вызывается геттер (getter), при записи – сеттер (setter), а при удалении – делитер (deleter). Геттер и делитер опциональны.
Краткая справка. Свойства нужны, чтобы элегантно в стиле ООП обрабатывать работу с полями класса. В ООП с полями класса не работают напрямую, они сокрыты. В интерфейс взаимодействия выводятся геттеры и сеттеры. Кроме того, геттер может производить вычисления, если свойство не хранится в классе, а является результатом какой-то математической формулы. А сеттер может проверять входные данные на корректность и вызывать побочные эффекты, например, сеттер при установке позиции картинки на экране может вызвать перерисовку экрана.
Декоратор @property
возвращает объект-дескриптор. О них я еще не рассказывал, но обязательно расскажу. @property
встроен и виден без import.
Примеры. Вес коробки не может быть отрицательным:
class Box: def __init__(self): self.__weight = 0 @property def weight(self): return self.__weight @weight.setter def weight(self, new_weight): if new_weight < 0: raise ValueError('negative weight') self.__weight = new_weight b = Box() b.weight = 100 print(b.weight) # 100 b.weight = -10 # ValueError
Вычислимое свойство:
class Circle: def __init__(self, r): self.r = r @property def area(self): return 3.1415 * self.r**2 c = Circle(10) print(c.area)
Статические и классовые методы
Методы могут быть не только у экземпляра класса, но и у самого класса, которые вызываются без какого-то экземпляра (без self). Декораторы @staticmethod
и @classmethod
как раз делают метод таким (статическим или классовым). Эти декораторы встроены и видны без import.
Статический метод – это способ поместить функцию в класс, если она логически относится к этому классу. Статический метод ничего не знает о классе, из которого его вызвали.
class Foo: @staticmethod def help(): print('help for Foo class') Foo.help()
Классовый метод напротив знает, из какого класса его вызывают. Он принимает неявный первый аргумент (обычно его зовут cls
), который содержит вызывающий класс. Классовые методы прекрасно подходят, когда нужно учесть иерархию наследования. Пример: метод group
создает список из нескольких людей. Причем для Person – список Person, а для Worker – список Worker. Со @staticmethod такое бы не вышло:
class Person: @classmethod def group(cls, n): # cls именно тот класс, который вызвал return [cls() for _ in range(n)] def __repr__(self): return 'Person' class Worker(Person): def __repr__(self): return 'Worker' print(Person.group(3)) # [Person, Person, Person] print(Worker.group(2)) # [Worker, Worker]
@contextmanager
Этот декоратор позволяет получить из генератора – контекст менеджер. Находится в стандартном модуле contextlib. Пример открытие файла.
from contextlib import contextmanager @contextmanager def my_open(name, mode='r'): # тут код для получения ресурса f = open(name, mode) print('Файл открыт:', name) try: yield f finally: # Code to release resource, e.g.: f.close() print('Файл закрыт:', name) # использование with my_open('1.txt', 'w') as f: f.write('Hello') f.fooooo() # <- error # Файл открыт: 1.txt # Traceback (most recent call last): # Файл закрыт: 1.txt
В этом генераторе есть единственный yield
– он возвращает как раз нужный ресурс. Все, что до него – код захвата ресурса (будет выполнен в методе __enter__
), например, открытие файла. Мы оборачиваем yield
в try/finally,
чтобы если даже в блоке кода with
произойдет ошибка, то исключение выбросится из yield
, но код закрытия файла в блоке finally
будет выполнен в любом случае. Код закрытия выполняется в методе __exit__
менеджера контекста.
Асинхронная версия этого декоратора – @asynccontextmanager
. Пример:
from contextlib import asynccontextmanager @asynccontextmanager async def get_connection(): conn = await acquire_db_connection() try: yield conn finally: await release_db_connection(conn) # использование async def get_all_users(): async with get_connection() as conn: return conn.query('SELECT ...')
Кэширование @lru_cache
Писал здесь подробно. Запоминает результаты функции для данного набора аргументов, при следующем вызове уже не выполняет функцию, а достает результат из кэша. Размер кэша регулируется. Часто используемые элементы остаются в кэше, редкие – вытесняются, если размер доходит до максимального.
Пример. Без кэша это рекурсивная функция чисел Фибоначчи была бы крайне неэффективна:
import functools @functools.lru_cache(maxsize=128) def fibonacci(n): if n == 0: return 0 elif n == 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2)
@functools.wraps
Декоратор @functools.wraps полезен при разработке других декораторов. Передает имя, документацию и прочую мета-информацию из декорируемой функции к ее обертке. Подробнее в статье про декораторы.
@atexit.register
Декоратор @atexit.register регистрирует функцию для вызова ее при завершении работы процесса Python.
import atexit @atexit.register def goodbye(): print("You are now leaving the Python sector.")
Измерение времени @timeit
Переходим к самописным декораторам.
Этот декоратор измеряет время выполнения функции, которую декорирует.
import time from functools import wraps def timeit(method): @wraps(method) def timed(*args, **kw): ts = time.monotonic() result = method(*args, **kw) te = time.monotonic() ms = (te - ts) * 1000 all_args = ', '.join(tuple(f'{a!r}' for a in args) + tuple(f'{k}={v!r}' for k, v in kw.items())) print(f'{method.__name__}({all_args}): {ms:2.2f} ms') return result return timed # использование: @timeit def slow_func(x, y, sleep): time.sleep(sleep) return x + y slow_func(10, 20, sleep=2) # печатает: slow_func(10, 20, sleep=2): 2004.65 ms
Как видите, нам не нужно вмешиваться в код функции, не нужно каждый раз писать измеритель времени, декоратор отлично экономит нашу работу: надо измерить время – дописали @timeit
и видим все, как на ладони.
Кэшируемое свойство класса
Свойство класса, которое реально вычисляется один раз, а потом запоминается на ttl секунд:
import time class CachedProperty: def __init__(self, ttl=300): self.ttl = ttl def __call__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ self.__module__ = fget.__module__ return self def __get__(self, inst, owner): now = time.monotonic() try: value, last_update = inst._cache[self.__name__] if self.ttl > 0 and now - last_update > self.ttl: raise AttributeError except (KeyError, AttributeError): value = self.fget(inst) try: cache = inst._cache except AttributeError: cache = inst._cache = {} cache[self.__name__] = (value, now) return value cached_property = CachedProperty # применение class Foo: @cached_property() def some_long_running_property(self): time.sleep(1) return 42 f = Foo() for _ in range(10): print(f.some_long_running_property)
Из 10 повторений только первое вызовет код геттера, а остальные возьмут значение из кэша.
Устаревший метод с @deprecated
При вызове метода или функции, помеченной декоратором @deprecated
будет выдано предупреждение, что этот метод или функция является устаревшими:
import logging from functools import wraps def deprecated(func): @wraps(func) def new_func(*args, **kwargs): logging.warning("Call to deprecated function {}.".format(func.__name__)) return func(*args, **kwargs) return new_func # примеры: @deprecated def some_old_function(x, y): return x + y class SomeClass: @deprecated def some_old_method(self, x, y): return x + y some_old_function(10, 20) # WARNING:root:Call to deprecated function some_old_function. SomeClass().some_old_method(20, 10) # WARNING:root:Call to deprecated function some_old_method.
Повторитель
Повторяет вызов функции n раз, возвращает последний результат.
from functools import wraps def repeat(_func=None, *, num_times=2): def decorator_repeat(func): @wraps(func) def wrapper_repeat(*args, **kwargs): value = None for _ in range(num_times): value = func(*args, **kwargs) return value return wrapper_repeat if _func is None: return decorator_repeat else: return decorator_repeat(_func) @repeat(num_times=5) def foo(): print('текст') foo() # текст # текст # текст # текст # текст
Замедлитель
Замедляет исполнение функции на нужное число секунд. Бывает полезно для отладки.
from functools import wraps import time def slow_down(seconds=1): def _slow_down(func): """Sleep 1 second before calling the function""" @wraps(func) def wrapper_slow_down(*args, **kwargs): time.sleep(seconds) return func(*args, **kwargs) return wrapper_slow_down return _slow_down @slow_down(seconds=0.5) def foo(): print('foo') def bar(): foo() # каждый foo по полсекунды foo() bar()
Помощник для отладки
Этот декоратор будет логгировать все вызовы функции и печатать ее аргументы и возвращаемое значение.
from functools import wraps # Печатает сигнатуру вызова и возвращаемое значение import functools def debug(func): @wraps(func) def wrapper_debug(*args, **kwargs): args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] signature = ", ".join(args_repr + kwargs_repr) print(f"Calling {func.__name__}({signature})") value = func(*args, **kwargs) print(f"{func.__name__!r} returned {value!r}") return value return wrapper_debug @debug def testee(x, y): print(x + y) testee(2, y=5) # Calling testee(2, y=5) # 7 # 'testee' returned None
Декораторы в Django и Flask
Декораторы активно задействованы в Django. Например, они позволяют налагать условия на обработку запросов или дополнять запрос дополнительной логикой:
@require_POST @login_required @transaction_atomic def some_view(request): ...
Пример с проверкой наличия зарегистрированного юзера для Flask:
from flask import Flask, g, request, redirect, url_for import functools app = Flask(__name__) def login_required(func): """Make sure user is logged in before proceeding""" @functools.wraps(func) def wrapper_login_required(*args, **kwargs): # если нет юзера, то редикректим на страницу логина if g.user is None: return redirect(url_for("login", next=request.url)) return func(*args, **kwargs) return wrapper_login_required @app.route("/secret") @login_required def secret(): ...
Еще хочу!
Вот ссылка на широкий набор декораторов и библиотек, их применяющих (на английском). Или вот тут.
🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈
1 224
Декораторы в Python — PYTHON
Тема декораторов довольно часто, однако, обсуждается и эта статья (выросшая из одного вопроса на stackoverflow) наиболее полно, по-моему мнению, раскрывает тему и, что немаловажно, является «пошаговым руководством» для использованию декораторов, позволяющим новичку овладеть этой техникой сразу на достойном уровне.
Итак, что же такое «декоратор»?
Впереди достаточно длинная статья, так что, если кто-то спешит — вот пример того, как работают декораторы:
def makebold(fn): def wrapped(): return "" + fn() + "" return wrapped def makeitalic(fn): def wrapped(): return "" + fn() + "" return wrapped @makebold @makeitalic def hello(): return "hello habr" print hello() ## выведет hello habr
Те же из вас, кто готов потратить немного времени, приглашаются прочесть длиииинный пост.
Функции в Python’e являются объектами
Для того, чтобы понять, как работают декораторы, в первую очередь следует осознать, что в Python’е функции — это тоже объекты.
Давайте посмотрим, что из этого следует:
def shout(word="да"): return word.capitalize()+"!" print shout() # выведет: 'Да!' # Так как функция - это объект, вы связать её с переменнной, # как и любой другой объект scream = shout # Заметьте, что мы не используем скобок: мы НЕ вызываем функцию "shout", # мы связываем её с переменной "scream". Это означает, что теперь мы # можем вызывать "shout" через "scream": print scream() # выведет: 'Да!' # Более того, это значит, что мы можем удалить "shout", и функция всё ещё # будет доступна через переменную "scream" del shout try: print shout() except NameError, e: print e #выведет: "name 'shout' is not defined" print scream() # выведет: 'Да!'
Ссылки на функции
Ну что, вы всё ещё здесь?:)
Теперь мы знаем, что функции являются полноправными объектами, а значит:
- могут быть связаны с переменной;
- могут быть определены одна внутри другой.
Что ж, а это значит, что одна функция может вернуть другую функцию!
Давайте посмотрим:
def getTalk(type="shout"): # Мы определяем функции прямо здесь def shout(word="да"): return word.capitalize()+"!" def whisper(word="да") : return word.lower()+"..."; # Затем возвращаем необходимую if type == "shout": # Заметьте, что мы НЕ используем "()", нам нужно не вызвать функцию, # а вернуть объект функции return shout else: return whisper # Как использовать это непонятное нечто? # Возьмём функцию и свяжем её с переменной talk = getTalk() # Как мы можем видеть, "talk" теперь - объект "function": print talk # выведет: # Который можно вызывать, как и функцию, определённую "обычным образом": print talk() # Если нам захочется - можно вызвать её напрямую из возвращаемого значения: print getTalk("whisper")() # выведет: да...
Подождите, раз мы можем возвращать функцию, значит, мы можем и передавать её другой функции, как параметр:
def doSomethingBefore(func): print "Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал" print func() doSomethingBefore(scream) #выведет: # Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал # Да!
Ну что, теперь у нас есть все необходимые знания для того, чтобы понять, как работают декораторы.
Как вы могли догадаться, декораторы — это, по сути, просто своеобразные «обёртки», которые дают нам возможность делать что-либо до и после того, что сделает декорируемая функция, не изменяя её.
Создадим свой декоратор «вручную»
# Декоратор - это функция, ожидающая ДРУГУЮ функцию в качестве параметра def my_shiny_new_decorator(a_function_to_decorate): # Внутри себя декоратор определяет функцию-"обёртку". # Она будет (что бы вы думали?..) обёрнута вокруг декорируемой, # получая возможность исполнять произвольный код до и после неё. def the_wrapper_around_the_original_function(): # Поместим здесь код, который мы хотим запускать ДО вызова # оригинальной функции print "Я - код, который отработает до вызова функции" # ВЫЗОВЕМ саму декорируемую функцию a_function_to_decorate() # А здесь поместим код, который мы хотим запускать ПОСЛЕ вызова # оригинальной функции print "А я - код, срабатывающий после" # На данный момент функция "a_function_to_decorate" НЕ ВЫЗЫВАЛАСЬ НИ РАЗУ # Теперь, вернём функцию-обёртку, которая содержит в себе # декорируемую функцию, и код, который необходимо выполнить до и после. # Всё просто! return the_wrapper_around_the_original_function # Представим теперь, что у нас есть функция, которую мы не планируем больше трогать. def a_stand_alone_function(): print "Я простая одинокая функция, ты ведь не посмеешь меня изменять?.." a_stand_alone_function() # выведет: Я простая одинокая функция, ты ведь не посмеешь меня изменять?.. # Однако, чтобы изменить её поведение, мы можем декорировать её, то есть # Просто передать декоратору, который обернет исходную функцию в любой код, # который нам потребуется, и вернёт новую, готовую к использованию функцию: a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() #выведет: # Я - код, который отработает до вызова функции # Я простая одинокая функция, ты ведь не посмеешь меня изменять?.. # А я - код, срабатывающий после
Наверное, теперь мы бы хотели, чтобы каждый раз, во время вызова a_stand_alone_function
, вместо неё вызывалась a_stand_alone_function_decorated
. Нет ничего проще, просто перезапишем a_stand_alone_function
функцией, которую нам вернул my_shiny_new_decorator
:
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() #выведет: # Я - код, который отработает до вызова функции # Я простая одинокая функция, ты ведь не посмеешь меня изменять?.. # А я - код, срабатывающий после
Вы ведь уже догадались, что это ровно тоже самое, что делают @декораторы.:)
Разрушаем ореол таинственности вокруг декораторов
Вот так можно было записать предыдущий пример, используя синтаксис декораторов:
@my_shiny_new_decorator def another_stand_alone_function(): print "Оставь меня в покое" another_stand_alone_function() #выведет: # Я - код, который отработает до вызова функции # Оставь меня в покое # А я - код, срабатывающий после
Да, всё действительно так просто! decorator
— просто синтаксический сахар для конструкций вида:
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
Декораторы — это просто pythonic-реализация паттерна проектирования «Декоратор». В Python включены некоторые классические паттерны проектирования, такие как рассматриваемые в этой статье декораторы, или привычные любому пайтонисту итераторы.
Конечно, можно вкладывать декораторы друг в друга, например так:
def bread(func): def wrapper(): print "------\>" func() print "" return wrapper def ingredients(func): def wrapper(): print "#помидоры#" func() print "~салат~" return wrapper def sandwich(food="--ветчина--"): print food sandwich() #выведет: --ветчина-- sandwich = bread(ingredients(sandwich)) sandwich() #выведет: # ------\> # #помидоры# # --ветчина-- # ~салат~ #
И используя синтаксис декораторов:
@bread @ingredients def sandwich(food="--ветчина--"): print food sandwich() #выведет: # ------\> # #помидоры# # --ветчина-- # ~салат~ #
Следует помнить о том, что порядок декорирования ВАЖЕН:
@ingredients @bread def sandwich(food="--ветчина--"): print food sandwich() #выведет: # #помидоры# # ------\> # --ветчина-- # # ~салат~
На этом моменте Вы можете счастливо уйти, с осознанием того, что вы поняли, что такое декораторы и с чем их едят.
Для тех же, кто хочет помучать ещё немного свой мозг, вторая часть статьи, посвящённая продвинутому использованию декораторов.
Декораторы, которые мы до этого рассматривали не имели одного очень важного функционала — передачи аргументов декорируемой функции.
Что ж, исправим это недоразумение!
Передача («проброс») аргументов в декорируемую функцию
Никакой чёрной магии, всё, что нам необходимо — собственно, передать аргументы дальше!
def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): # аргументы прибывают отсюда print "Смотри, что я получил:", arg1, arg2 function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments # Теперь, когда мы вызываем функцию, которую возвращает декоратор, # мы вызываем её "обёртку", передаём ей аргументы и уже в свою очередь # она передаёт их декорируемой функции @a_decorator_passing_arguments def print_full_name(first_name, last_name): print "Меня зовут", first_name, last_name print_full_name("Питер", "Венкман") # выведет: # Смотри, что я получил: Питер Венкман # Меня зовут Питер Венкман # *
* — Прим. переводчика: Питер Венкман — имя одного из Охотников за приведениями, главного героя одноименного культового фильма.
Декорирование методов
Один из важных фактов, которые следует понимать, заключается в том, что функции и методы в Python’e — это практически одно и то же, за исключением того, что методы всегда ожидают первым параметром ссылку на сам объект (self
). Это значит, что мы можем создавать декораторы для методов так же, как и для функций, просто не забывая про self
.
def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 # действительно, дружелюбно - снизим возраст ещё сильней :-) return method_to_decorate(self, lie) return wrapper class Lucy(object): def __init__(self): self.age = 32 @method_friendly_decorator def sayYourAge(self, lie): print "Мне %s, а ты бы сколько дал?" % (self.age + lie) l = Lucy() l.sayYourAge(-3) # выведет: Мне 26, а ты бы сколько дал?
Конечно, если мы создаём максимально общий декоратор и хотим, чтобы его можно было применить к любой функции или методу, то стоит воспользоваться тем, что *args
распаковывает список args
, а **kwargs
распаковывает словарь kwargs
:
def a_decorator_passing_arbitrary_arguments(function_to_decorate): # Данная "обёртка" принимает любые аргументы def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print "Передали ли мне что-нибудь?:" print args print kwargs # Теперь мы распакуем *args и **kwargs # Если вы не слишком хорошо знакомы с распаковкой, можете прочесть следующую статью: # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) return a_wrapper_accepting_arbitrary_arguments @a_decorator_passing_arbitrary_arguments def function_with_no_argument(): print "Python is cool, no argument here." # оставлено без перевода, хорошая игра слов:) function_with_no_argument() # выведет: # Передали ли мне что-нибудь?: # () # {} # Python is cool, no argument here. @a_decorator_passing_arbitrary_arguments def function_with_arguments(a, b, c): print a, b, c function_with_arguments(1,2,3) # выведет: # Передали ли мне что-нибудь?: # (1, 2, 3) # {} # 1 2 3 @a_decorator_passing_arbitrary_arguments def function_with_named_arguments(a, b, c, platypus="Почему нет?"): print "Любят ли %s, %s и %s утконосов? %s" %\ (a, b, c, platypus) function_with_named_arguments("Билл", "Линус", "Стив", platypus="Определенно!") # выведет: # Передали ли мне что-нибудь?: # ('Билл', 'Линус', 'Стив') # {'platypus': 'Определенно!'} # Любят ли Билл, Линус и Стив утконосов? Определенно! class Mary(object): def __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge(self, lie=-3): # Теперь мы можем указать значение по умолчанию print "Мне %s, а ты бы сколько дал?" % (self.age + lie) m = Mary() m.sayYourAge() # выведет: # Передали ли мне что-нибудь?: # (<__main__ .Mary object at 0xb7d303ac>,) # {} # Мне 28, а ты бы сколько дал?
Вызов декоратора с различными аргументами
Отлично, с этим разобрались. Что вы теперь скажете о том, чтобы попробовать вызывать декораторы с различными аргументами?
Это не так просто, как кажется, поскольку декоратор должен принимать функцию в качестве аргумента, и мы не можем просто так передать ему что либо ещё.
Так что, перед тем, как показать вам решение, я бы хотел освежить в памяти то, что мы уже знаем:
# Декораторы - это просто функции def my_decorator(func): print "Я обычная функция" def wrapper(): print "Я - функция, возвращаемая декоратором" func() return wrapper # Так что, мы можем вызывать её, не используя "@"-синтаксис: def lazy_function(): print "zzzzzzzz" decorated_function = my_decorator(lazy_function) # выведет: Я обычная функция # Данный код выводит "Я обычная функция", потому что это ровно то, что мы сделали: # вызвали функцию. Ничего сверхъестественного @my_decorator def lazy_function(): print "zzzzzzzz" # выведет: Я обычная функция
Как мы видим, это два аналогичных действия. Когда мы пишем @my_decorator
— мы просто говорим интерпретатору «вызвать функцию, под названием my_decorator
». Это важный момент, потому что данное название может как привести нас напрямую к декоратору… так и нет!
Давайте сделаем нечто страшное!:)
def decorator_maker(): print "Я создаю декораторы! Я буду вызван только раз: "+\ "когда ты попросишь меня создать тебе декоратор." def my_decorator(func): print "Я - декоратор! Я буду вызван только раз: в момент декорирования функции." def wrapped(): print ("Я - обёртка вокруг декорируемой функции. " "Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. " "Я возвращаю результат работы декорируемой функции.") return func() print "Я возвращаю обёрнутую функцию." return wrapped print "Я возвращаю декоратор." return my_decorator # Давайте теперь создадим декоратор. Это всего лишь ещё один вызов функции new_decorator = decorator_maker() # выведет: # Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. # Я возвращаю декоратор. # Теперь декорируем функцию def decorated_function(): print "Я - декорируемая функция." decorated_function = new_decorator(decorated_function) # выведет: # Я - декоратор! Я буду вызван только раз: в момент декорирования функции. # Я возвращаю обёрнутую функцию. # Теперь наконец вызовем функцию: decorated_function() # выведет: # Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. # Я возвращаю результат работы декорируемой функции. # Я - декорируемая функция.
Длинно? Длинно. Перепишем данный код без использования промежуточных переменных:
def decorated_function(): print "Я - декорируемая функция." decorated_function = decorator_maker()(decorated_function) # выведет: # Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. # Я возвращаю декоратор. # Я - декоратор! Я буду вызван только раз: в момент декорирования функции. # Я возвращаю обёрнутую функцию. # Наконец: decorated_function() # выведет: # Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. # Я возвращаю результат работы декорируемой функции. # Я - декорируемая функция.
А теперь ещё раз, ещё короче:
@decorator_maker() def decorated_function(): print "I am the decorated function." # выведет: # Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. # Я возвращаю декоратор. # Я - декоратор! Я буду вызван только раз: в момент декорирования функции. # Я возвращаю обёрнутую функцию. # И снова: decorated_function() # выведет: # Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. # Я возвращаю результат работы декорируемой функции. # Я - декорируемая функция.
Вы заметили, что мы вызвали функцию, после знака «@»?:)
Вернёмся, наконец, к аргументам декораторов, ведь если мы используем функцию, чтобы создавать декораторы «на лету», мы можем передавать ей любые аргументы, верно?
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2): print "Я создаю декораторы! И я получил следующие аргументы:", decorator_arg1, decorator_arg2 def my_decorator(func): print "Я - декоратор. И ты всё же смог передать мне эти аргументы:", decorator_arg1, decorator_arg2 # Не перепутайте аргументы декораторов с аргументами функций! def wrapped(function_arg1, function_arg2) : print ("Я - обёртка вокруг декорируемой функции.\n" "И я имею доступ ко всем аргументам: \n" "\t- и декоратора: {0} {1}\n" "\t- и функции: {2} {3}\n" "Теперь я могу передать нужные аргументы дальше" .format(decorator_arg1, decorator_arg2, function_arg1, function_arg2)) return func(function_arg1, function_arg2) return wrapped return my_decorator @decorator_maker_with_arguments("Леонард", "Шелдон") def decorated_function_with_arguments(function_arg1, function_arg2): print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}" " {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments("Раджеш", "Говард") # выведет: # Я создаю декораторы! И я получил следующие аргументы: Леонард Шелдон # Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Шелдон # Я - обёртка вокруг декорируемой функции. # И я имею доступ ко всем аргументам: # - и декоратора: Леонард Шелдон # - и функции: Раджеш Говард # Теперь я могу передать нужные аргументы дальше # Я - декорируемая функция и я знаю только о своих аргументах: Раджеш Говард
* — Прим. переводчика: в данном примере автор упоминает имена главных героев популярного сериала «Теория Большого взрыва».
Вот он, искомый декоратор, которому можно передавать произвольные аргументы.
Безусловно, аргументами могут быть любые переменные:
c1 = "Пенни" c2 = "Лесли" @decorator_maker_with_arguments("Леонард", c1) def decorated_function_with_arguments(function_arg1, function_arg2): print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}" " {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments(c2, "Говард") # выведет: # Я создаю декораторы! И я получил следующие аргументы: Леонард Пенни # Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Пенни # Я - обёртка вокруг декорируемой функции. # И я имею доступ ко всем аргументам: # - и декоратора: Леонард Пенни # - и функции: Лесли Говард # Теперь я могу передать нужные аргументы дальше # Я - декорируемая функция и я знаю только о своих аргументах: Лесли Говард
Таким образом, мы можем передавать декоратору любые аргументы, как обычной функции. Мы можем использовать и распаковку через *args
и **kwargs
в случае необходимости.
Но необходимо всегда держать в голове, что декоратор вызывается ровно один раз. Ровно в момент, когда Python импортирует Ваш скрипт. После этого мы уже не можем никак изменить аргументы, с которыми.
Когда мы пишем «import x
» все функции из x
декорируются сразу же, и мы уже не сможем ничего изменить.
Немного практики: напишем декоратор декорирующий декоратор
Если вы дочитали до этого момента и ещё в строю — вот вам бонус от меня.
Это небольшая хитрость позволит вам превратить любой обычный декоратор в декоратор, принимающий аргументы.
Изначально, чтобы получить декоратор, принимающий аргументы, мы создали его с помощью другой функции.
Мы обернули наш декоратор.
Есть ли у нас что-нибудь, чем можно обернуть функцию?
Точно, декораторы!
Давайте же немного развлечёмся и напишем декоратор для декораторов:
def decorator_with_args(decorator_to_enhance): """ Эта функция задумывается КАК декоратор и ДЛЯ декораторов. Она должна декорировать другую функцию, которая должна быть декоратором. Лучше выпейте чашку кофе. Она даёт возможность любому декоратору принимать произвольные аргументы, избавляя Вас от головной боли о том, как же это делается, каждый раз, когда этот функционал необходим. """ # Мы используем тот же трюк, который мы использовали для передачи аргументов: def decorator_maker(*args, **kwargs): # создадим на лету декоратор, который принимает как аргумент только # функцию, но сохраняет все аргументы, переданные своему "создателю" def decorator_wrapper(func): # Мы возвращаем то, что вернёт нам изначальный декоратор, который, в свою очередь # ПРОСТО ФУНКЦИЯ (возвращающая функцию). # Единственная ловушка в том, что этот декоратор должен быть именно такого # decorator(func, *args, **kwargs) # вида, иначе ничего не сработает return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_maker
Это может быть использовано так:
# Мы создаём функцию, которую будем использовать как декоратор и декорируем её :-) # Не стоит забывать, что она должна иметь вид "decorator(func, *args, **kwargs)" @decorator_with_args def decorated_decorator(func, *args, **kwargs): def wrapper(function_arg1, function_arg2): print "Мне тут передали...:", args, kwargs return func(function_arg1, function_arg2) return wrapper # Теперь декорируем любую нужную функцию нашим новеньким, ещё блестящим декоратором: @decorated_decorator(42, 404, 1024) def decorated_function(function_arg1, function_arg2): print "Привет", function_arg1, function_arg2 decorated_function("Вселенная и", "всё прочее") # выведет: # Мне тут передали...: (42, 404, 1024) {} # Привет Вселенная и всё прочее # Уфффффф!
Думаю, я знаю, что Вы сейчас чувствуете.
Последний раз Вы испытывали это ощущение, слушая, как вам говорят: «Чтобы понять рекурсию необходимо для начала понять рекурсию».
Но ведь теперь Вы рады, что разобрались с этим?;)
Рекомендации для работы с декораторами
- Декораторы были введены в Python 2.4, так что узнавайте, на чём будет выполняться Ваш код.
- Декораторы несколько замедляют вызов функции, не забывайте об этом.
- Вы не можете «раздекорировать» функцию. Безусловно, существуют трюки, позволяющие создать декоратор, который можно отсоединить от функции, но это плохая практика. Правильней будет запомнить, что если функция декорирована — это не отменить.
- Декораторы оборачивают функции, что может затруднить отладку.
Последняя проблема частично решена в Python 2.5, добавлением в стандартную библиотеку модуля functools
включающего в себя functools.wraps
, который копирует всю информацию об оборачиваемой функции (её имя, из какого она райомодуля, её docstrings и т.п.) в функцию-обёртку.
Забавным фактом является то, что functools.wraps
— сам по себе декоратор.
# Во время отладки, в трассировочную информацию выводится __name__ функции. def foo(): print "foo" print foo.__name__ # выведет: foo # Однако, декораторы мешают нормальному ходу дел: def bar(func): def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" print foo.__name__ # выведет: wrapper # "functools" может нам с этим помочь import functools def bar(func): # Объявляем "wrapper" оборачивающим "func" # и запускаем магию: @functools.wraps(func) def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" print foo.__name__ # выведет: foo
Как можно использовать декораторы?
И в заключение, я бы хотел ответить на вопрос, который я часто слышу: зачем же нужны декораторы? Как их можно использовать?
Декораторы могут быть использованы для расширения возможностей функций из сторонних библиотек (код которых мы не можем изменять), или для упрощения отладки (мы не хотим изменять код, который ещё не устоялся).
Так же полезно использовать декораторы для расширения различных функций одним и тем же кодом, без повторного его переписывания каждый раз, например:
def benchmark(func): """ Декоратор, выводящий время, которое заняло выполнение декорируемой функции. """ import time def wrapper(*args, **kwargs): t = time.clock() res = func(*args, **kwargs) print func.__name__, time.clock() - t return res return wrapper def logging(func): """ Декоратор, логирующий работу кода. (хорошо, он просто выводит вызовы, но тут могло быть и логирование!) """ def wrapper(*args, **kwargs): res = func(*args, **kwargs) print func.__name__, args, kwargs return res return wrapper def counter(func): """ Декоратор, считающий и выводящий количество вызовов декорируемой функции. """ def wrapper(*args, **kwargs): wrapper.count += 1 res = func(*args, **kwargs) print "{0} была вызвана: {1}x".format(func.__name__, wrapper.count) return res wrapper.count = 0 return wrapper @benchmark @logging @counter def reverse_string(string): return str(reversed(string)) print reverse_string("А роза упала на лапу Азора") print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!") # выведет: # reverse_string ('А роза упала на лапу Азора',) {} # wrapper 0.0 # reverse_string была вызвана: 1x # арозА упал ан алапу азор А # reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {} # wrapper 0.0 # reverse_string была вызвана: 2x # !amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A
Таким образом, декораторы можно применить к любой функции, расширив её функционал и не переписывая ни строчки кода!
import httplib @benchmark @logging @counter def get_random_futurama_quote(): conn = httplib.HTTPConnection("slashdot.org:80") conn.request("HEAD", "/index.html") for key, value in conn.getresponse().getheaders(): if key.startswith("x-b") or key.startswith("x-f"): return value return "Эх, нет... не могу!" print get_random_futurama_quote() print get_random_futurama_quote() #outputs: #get_random_futurama_quote () {} #wrapper 0.02 #get_random_futurama_quote была вызвана: 1x #The laws of science be a harsh mistress. #get_random_futurama_quote () {} #wrapper 0.01 #get_random_futurama_quote была вызвана: 2x #Curse you, merciful Poseidon!
В Python включены такие декораторы как property, staticmethod
и т.д.
В Django декораторы используются для управления кешированием, контроля за правами доступа и определения обработчиков адресов. В Twisted — для создания поддельных асинхронных inline-вызовов.
Декораторы открывают широчайший простор для экспериментов! И надеюсь, что данная статья поможет Вам в его освоении!
Спасибо за внимание!
Источники вдохновения:
Python: декораторы
У Python есть интересная возможность, которая называется «декораторы» (decorators), которая позволяет добавлять функциональность к уже существующему коду. Такая возможность так же называется метапрограммированием — когда одна часть программы пытается модифицировать другую часть во время компиляции.
Введение
Что бы понять суть работы декораторов — мы сначала должны рассмотреть несколько базовых концепций Python. Во-первых — мы должны понимать, что абсолютно всё в Python вляется объектами. Имена, которые мы определяем, являются просто указателями к этим объектам. Функции не являются исключением из этого правила — они тоже являются объектами с атрибутами. Несколько различных имён могут быть связаны с одним и тем же объектом. Например:
>>> def first(msg): ... print(msg) ... >>> first("Hello") Hello >>> second = first >>> second("Hello") Hello
Тут имена first
и second
связаны с одним и тем же объектом функции.
Теперь — рассмотрим более странные вещи. Функции могут передаваться в качестве аргументов другим функциям. Если вы уже использовали такие функции как map()
, filter(
) или reduce()
в Python — значит вы уже сталкивались с таким подходом. Функции, которые принимают другие функции в качестве аргументов называются функциями высшего порядка (higher order functions). Вот пример такой функции:
def inc(x): """Function to increase value by 1""" return x + 1 def dec(x): """Function to decrease value by 1""" return x - 1 def operate(func, x): """A higer order function to increase or decrease""" result = func(x) return result
Далее — мы можем вызвать функцию operate()
так:
>>> operate(inc,3) 4 >>> operate(dec,3) 2
Кроме того — функция может возвращать другую функцию:
>>> def is_called(): ... def is_returned(): ... print("Hello") ... return is_returned ... >>> new = is_called() >>> new() Hello
Тут функция is_returned()
является вложенной функцией, которая инициализируется и возвращается каждый раз, когда вызывается функция is_called()
.
Кроме того — было полезно бы знать о замыканиях в Python (to be traslated :-)).
Декораторы
Фактически, любой объект, который имеет специальный метод __cal__()
является вызываемым. В общем смысле — декораторы являются вызываемыми объектами, которые возвращают другой вызываемый объект. Как правило — декоратор принимает функцию, добавляет некоторую функциональность к ней, и возвращает её:
>>> def make_pretty(func): ... def inner(): ... print("I got decorated") ... func() ... return inner ... >>> def ordinary(): ... print("I am ordinary") ...
>>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary
В этом примере make_pretty()
является декотратором. Во время присвоения:
pretty = make_pretty(ordinary)
Функция ordinary()
становится «декорированной», а функции, которая возвращается из make_pretty()
присваивается имя pretty
. Мы видим, что декоратор добавил новую функциональность к оригинальной функции ordinary()
. Этот процесс схож с упаковкой подарка — декторатор выполняет роль обёртки. Суть поведения объекта, которые подвергся «декорации» (как подарок внутри упаковки) — не меняется.
Обычно мы «декорируем» функцию и присваиваем ей какое-то имя, как в примере выше:
ordinary = make_pretty(ordinary)
Это распростраённое действие, поэтому у Python имеется специальный синтаксис для таких случаев — мы можем использовать символ @
с именем функции-декоторатора, и разместить её перед функцией, которая будет передана декоратору, например — такой код:
>>> @make_pretty ... def ordinary(): ... print("I am ordinary")
Будет обработан аналогично такому:
>>> def ordinary(): ... print("I am ordinary") ... >>> ordinary = make_pretty(ordinary)
Функции-декоторы с параметрами
Декоратор в предыдущем примере был очень простым, и он сработает для функций, у которых нет парамтеров. Что если у нас будет функция, которая имеет несколько параметров? Например:
>>> def divide(a, b): ... return a/b
У этой функции есть два параметра — a
и b,
и мы знаем, что получим ошибку, если передадим 0 в b
:
>>> divide(2,5) 0 >>> divide(2,0) Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero
Теперь — давайте создадим декоратор, который будет проверять условие на наличие недопустимого действия:
>>> def smart_divide(func): ... def inner(a,b): ... print("I am going to divide",a,"and",b) ... if b == 0: ... print("Whoops! cannot divide") ... return ... return func(a,b) ... return inner ... >>> @smart_divide ... def divide(a,b): ... return a/b
Такое представление вернёт None
, если условие ошибки сработает:
>>> divide(2,5) ('I am going to divide', 2, 'and', 5) 0 >>> divide(2,0) ('I am going to divide', 2, 'and', 0) Whoops! cannot divide
Таким образом мы можем декорировать функции, которые имеют параметры. Внимательный читатель должен был заметить, что параметры вложенной функции inner()
внутри декоратора аналогичны параметрам функции, которая передаётся декоратору. Зная это — мы можем создать общий декоратор, который будет работать с любым количеством параметров. В Python это возможно с помощью function(*args, **kwargs)
. Таким образом — args
будут являтся кортежем позиционных аргументов, а kwargs
— словарём с ключевыми словами аргументов (именованные параметры).
Вот пример такого декоратора:
>>> def works_for_all(func): ... def inner(*args, **kwargs): ... print("I can decorate any function") ... return func(*args, **kwargs) ... return inner ... >>> @works_for_all ... def foo(a, b, c): ... print a, b, c
>>> foo('a', 'b', 'c') I can decorate any function a b c
Цепочки декораторов в Python
Несколько декораторов могут быть связаны в одну цепочку. Т.е. — одна функция может быть «декорирована» разными (или одним и тем же) декораторами. Мы просто располагаем декораторы перед желаемой функцией:
>>> def star(func): ... def inner(*args, **kwargs): ... print("*" * 30) ... func(*args, **kwargs) ... print("*" * 30) ... return inner ... >>> def percent(func): ... def inner(*args, **kwargs): ... print("%" * 30) ... func(*args, **kwargs) ... print("%" * 30) ... return inner ... >>> @star ... @percent ... def printer(msg): ... print(msg)
В результате — мы получим такой вывод:
>>> printer("Hello") ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************
Синтаксис:
@star @percent def printer(msg): print(msg)
Аналогичен использованию:
def printer(msg): print(msg) printer = star(percent(printer))
Учтите, что порядок указания декораторов имеет значение. Если мы поменяем их местами:
>>> @percent ... @star ... def printer(msg): ... print(msg)
То получим такой результат:
>>> printer("Hello") %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Оригинал статьи — тут>>>.
Декораторы в Python — tirinox.ru
Вероятно, почти каждый разработчик на Python сталкивался с декораторами, видя конструкцию с со знаком @:
@app.route('/') def index(): return "Hello, World!"
Разберемся, что такое декоратор, и как он работает. Этот вопрос часто спрашивают на собеседованиях.
Декоратор – это функция, которая принимает как аргумент другую функцию*. Цель декоратора – расширить функциональность переданной ему функции без непосредственного изменения кода самой функции. Вот и все!
* Примечание: декорировать можно и класс, но об этом расскажу потом!
В Python функция – тоже объект, и ее можно передавать как аргумент, возвращать из другой функции, ей также можно назначать атрибуты.
Символ собачка (@) – всего лишь синтаксический сахар:
@decorator def foo(): ... # эквивалентно: def foo(): ... foo = decorator(foo)
Теперь, зная, что декоратор всего лишь функция – попробуем разобраться, что она может делать и как должна выглядеть. Декоратор может возвращать, что угодно, но по смыслу вернуть надо тоже функцию, чтобы заменить ей оригинальную функцию. Тривиальный декоратор возвращает свой аргумент, не делая ничего:
def decorator(f): return f
А можно вернуть и вообще другую функцию. Например, определенную внутри декоратора (да, внутри функций можно определять другие функции):
def decorator(f): def inner(): print('inner') return inner
В зависимости от назначения декоратора во внутренней функции мы можем вызывать сколько угодно раз исходную функцию, обрамлять вызов любым кодом и т. п. Например, декоратор, который печатает сообщения о начале и конце работы функции:
def decorator(f): def inner(): print('begin') f() print('end') return inner @decorator def foo(): print('foo') foo() # begin foo # foo # end foo
Здесь используется «замыкание», которое сохраняет переменную f в особой области видимости (enclosing), что дает возможность обращаться к f из inner после выхода из функции decorator.
Часто неизвестно, какие аргументы принимает f. Поэтому аргументы обычно обобщают, называя их *args (все позиционные аргументы, как список), **kwargs (все именованные аргументы, как словарь). Эти две штуки охватывают все возможные аргументы. Так же у функции может быть возвращаемое значение, которое неплохо также вернуть из inner. Улучшим наш декоратор, добавив прозрачную передачу любых (заранее неизвестных) аргументов и возвращение результата декорированной функции:
def decorator(f): def inner(*args, **kwargs): print('begin') result = f(*args, **kwargs) print('end') return result return inner @decorator def foo(x, y): print(f'summing {x} and {y}...') return x + y print(foo(5, y=10)) # begin # summing 5 and 10... # end # 15
@wraps
Помимо самого кода функции и ее аргументов, у нее также есть и другие свойства, например ее настоящее имя или «docstring» (это строчка с описанием функции в начале ее тела, которая хранится в атрибуте __doc__
и выводится при вызове help). Декоратор из прошлого примера потеряет документацию и имя функции (она станет зваться inner):
@decorator def foo(x, y): """Doc string here""" return x + y help(foo) # Help on function inner in module __main__: # inner(*args, **kwargs)
Для того, чтобы предотвратить потерю атрибутов декорированной функции в модуле functools есть декоратор wraps. Да, еще один декоратор, которым мы декорируем inner в нашем декораторе.
Шутка про @wraps
Вот теперь название и документация поступят в обертку из оригинальной функции:
from functools import wraps def decorator(f): @wraps(f) def inner(*args, **kwargs): print('begin') result = f(*args, **kwargs) print('end') return result return inner @decorator def foo(x, y): """Doc string here""" return x + y help(foo) # Help on function foo in module __main__: # foo(x, y) # Doc string here
Композиция декораторов
Можно применить несколько декораторов к одной функции. Вообще говоря, результат зависит от порядка следования декораторов: тот, что ближе к определению функции воздействует на нее раньше того, что дальше. Пример декораторов для пары HTML тэгов (для простоты я опустил формальности передачи аргументов и атрибутов из предыдущей части). foo и bar декорированы в разном порядке:
def bold(f): def inner(): return '<b>' + f() + '</b>' return inner def italic(f): def inner(): return '<i>' + f() + '</i>' return inner @bold @italic def foo(): return 'foo text' @italic @bold def bar(): return 'bar text' print(foo()) # <b><i>foo text</i></b> print(bar()) # <i><b>bar text</b></i>
И результат разный, потому что:
foo = italic(bold(foo)) bar = bold(italic(bar))
Пока хватит. В следующих частях:
- Параметры для декораторов
- Декоратор как класс
- Декорирование методов класса
- Примеры декораторов свои и из стандартных модулей
- Декорирование классов
🧙 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈
655
Простое знакомство с декораторами и декором
Требуется помощь
На этом веб-сайте нет назойливой рекламы. Мы хотим, чтобы это было так. Вы можете помочь с пожертвованием:
Потребность в пожертвовании
Бернд Кляйн на Facebook
Искать на сайте:
Немецкая версия / Deutsche Übersetzung
Zur deutschen Webseite: Einführung в Dekorateure
Python 3
Это учебное пособие на Python3, но эта глава нашего курса доступна в версии для Python 2.x также: Простое введение в декораторы и декорации в Python 2.x
Очные курсы
Из-за пандемии короны мы в настоящее время проводим все курсы онлайн. Дальнейшая информация!
Целью этого веб-сайта является предоставление учебных материалов,
позволяя вам изучать Python самостоятельно.
Тем не менее, быстрее и эффективнее попасть на «настоящий»
Курс Python в классе, с
опытный тренер. Так почему бы не побывать на одном из концертов
Курсы Python в Страсбурге, Париже, Люксембурге, Амстердаме, Цюрихе / Цюрихе, Вене / Вене, Лондоне, Берлине, Мюнхене, Гамбурге, Франкфурте,
или Боденское озеро Бернда Кляйна, автора этого руководства?
Курсы обучения на месте
Из-за пандемии короны в настоящее время мы проводим все курсы онлайн.Дальнейшая информация!
Позвольте нам приехать в вашу компанию или институт и обучить ваших сотрудников, как мы делали это много раз в Амстердаме (Нидерланды), Берлине (Германия), Берне (Швейцария), Базеле (Швейцария), Цюрихе (Швейцария), Локарно. (Швейцария), Гаага (Гаага), Гамбург (Германия), Франкфурт (Германия), Торонто (Канада), Эдмонтон (Канада), Мюнхен (Германия), Вена / Вена (Австрия) и многие другие города.
Мы проводим учебные курсы в Англии, Швейцарии, Лихтенштейне, Австрии, Германии,
Франция, Бельгия, Нидерланды, Люксембург, Польша, Великобритания, Италия и другие регионы Европы и Канады.
Таким образом, вы получите идеальное обучение в соответствии с вашими потребностями и при этом будет чрезвычайно экономичным.
Свяжитесь с нами, чтобы мы могли определить и найти лучшую учебную программу, отвечающую вашим потребностям, и запланировать занятия курсов, которые будут проводиться в вашем регионе.
Квалифицированные программисты на Python
Вы ищете опытных разработчиков Python или программистов? Мы можем вам помочь, пожалуйста
Связаться с нами.
.Учебное пособие по
Python Decorator — Цепочка декораторов, Python Pie Syntax
1. Python Decorator — цель
В этом руководстве по Python Decorator мы изучим, что такое декоратор в Python и почему мы используем вложенные функции Python. Наряду с этим мы изучим декораторы Python с параметрами и синтаксис Python Pie. Наконец, мы изучим Chaining Decorators на языке программирования Python.
В Python функция — это первоклассный объект. Это означает, что вы можете с легкостью передать его.Вы можете вернуть его и даже передать в качестве аргумента другому. Вы также можете вложить функцию Python в другую.
Итак, приступим к руководству по Python Decorator.
Python Decorator Tutorial — связывание декораторов, синтаксис Python Pie
2. Что такое декоратор Python?
Функция Python Decorator — это функция, которая добавляет функциональность другой, но не изменяет ее. Другими словами, Python Decorator оборачивает другую функцию. Это как подарочная упаковка в реальной жизни.Также это называется метапрограммированием, потому что часть программы пытается изменить другую во время компиляции. В оставшейся части урока мы подробно рассмотрим синтаксис python декоратора Python.
Это полезно в тех случаях, когда вы хотите добавить функции к функции, но не хотите изменять ее. Давайте взглянем.
3. Простой декоратор Python
Если говорить о декораторе Python в первый раз, это может сбить с толку. Итак, мы начнем с очень простого примера без аргументов.Возьми этот код.
>>> def decor (func): def wrap (): print ("$$$$$$$$$$$$$$$$$$$$$") func () print ("$$$$$$$$$$$$$$$$$$$$$") возвратная упаковка >>> def sayhello (): print ("Привет") >>> newfunc = декор (Sayhello) >>> newfunc ()
$$$$$$$$$$$$$$$$$$$$$$
Привет
$$$$$$$$$$$$$$$$$$$$$$
Теперь давайте рассмотрим каждую часть синтаксиса по очереди. Также прочтите Recursion in Python.
а. Функция декоратора Python
Сначала мы определяем простую функцию sayhello (), которая выводит «Hello». Теперь мы определим функцию декоратора в Python. Вы можете назвать это как угодно; это не обязательно должен быть «декор». Это функция более высокого порядка. Обратите внимание, что относительный порядок этих функций не имеет значения. Вы можете определить sayhello () до определения decor (), и это не будет иметь никакого значения. Давайте подробно обсудим функцию декора.
def decor (func):
Первое, на что следует обратить внимание, это то, что он принимает функцию в качестве аргумента.Это функция, которую мы хотим украсить. Мы хотим назвать это func; вы можете назвать это как-нибудь иначе. Внутри этой функции мы вкладываем функцию и называем ее wrap (). Опять же, вы можете называть это как угодно.
г. Вложенная функция переноса
Именно в эту функцию мы помещаем нашу дополнительную функциональность, а также вызываем функцию, которую нужно оформить.
def wrap (): print ("$$$$$$$$$$$$$$$$$$$$$") func () print ("$$$$$$$$$$$$$$$$$$$$$")
Здесь мы использовали несколько операторов печати.Это могло быть и что угодно, например, if-block.
Наконец, мы заставляем функцию decor () возвращать функцию переноса.
возвратная пленка
Почему мы это делаем? Мы обсудим это далее в этом уроке.
г. Назначение и вызов
Наконец, мы назначаем этот декоратор Python переменной и передаем функцию, которая будет декорирована, в качестве аргумента функции декорирования.
newfunc = декор (sayhello)
Затем мы вызываем функцию, используя круглые скобки после переменной, которой мы назначаем декораторы в
питон.
newfunc ()
Однако вы также можете назначить это самой функции, которая будет украшена. Это переназначит его. Посмотрим и на это.
>>> def sayhello (): print ("Привет") >>> def decor (func): def wrap (): печать ("$") func () печать ("$") возвратная упаковка >>> sayhello = декор (Sayhello) >>> sayhello ()
$
Привет
$
Перед тем, как продолжить Прочтите Функции Python с синтаксисом и примерами.
4. Зачем нужна вложенная функция Python?
Когда я пытался понять декоратор Python, этот вопрос меня полностью сбил с толку. Почему мы используем функцию переноса, а затем возвращаем ее? Разве мы не могли бы просто написать код внутри функции декора? Так что я остановился на переводчике, попробовал его.
>>> def decor (func): печать ("$") func () печать ("$") >>> def sayhello (): print ("Привет")
Здесь мы написали дополнительную функциональность прямо внутри нашей функции decor ().А теперь давайте попробуем присвоить его переменной.
>>> newfunc = декор (sayhello)
$
Привет
$
Вау. Зачем распечатал? Это потому, что decor () вызывает функцию (здесь func) вместо возврата значения. Когда мы используем wrap (или как бы вы его назвали), а затем возвращали его, мы можем сохранить его в переменной. Затем мы можем использовать это имя для вызова декорированной функции, когда захотим. Теперь давайте вызовем функцию newfunc ().
>>> newfunc ()
Traceback (последний звонок последний):
Файл «
newfunc ()
TypeError: объект «NoneType» не вызывается
Как видите, поскольку декор не возвратил значение, строка назначения не присвоила декорированную функцию newfunc.Вот почему его нельзя отозвать. Таким образом, невозможно снова получить доступ к этому декоратору Python, кроме следующего метода:
>>> декор (сайелло)
$
Привет
$
Наконец, давайте попробуем вызвать нашу исходную функцию sayhello ().
>>> sayhello ()
Привет
Работает отлично. Видите ли, decor () не изменял sayhello ().
Мы используем один и тот же пример повсюду, чтобы вы могли сосредоточиться на том, что вам объясняется, и не тратиться на попытки понять код.
5. Декоратор Python с параметрами
До сих пор мы видели в Python декораторы только с обычными операторами печати. А теперь давайте перейдем к настоящему. Чтобы увидеть, как декораторы справляются с параметрами, мы рассмотрим пример функции, которая делит два значения. Все, что делает наша функция, это возвращает деление двух чисел. Но когда мы его украшаем, мы добавляем функциональность, чтобы справиться с ситуацией, когда номинал равен 0. Посмотрите, как.
>>> def div (a, b): вернуть а / б >>> Декоратор def (func): def обертка (a, b): если b == 0: print («Нельзя делить на 0!») возвращение return func (a, b) возвратная обертка
Как видите, функция декоратора Python принимает один аргумент для функции, которая будет украшать.Обертка здесь принимает те же аргументы, что и функция для украшения. Наконец, мы возвращаем декорируемую функцию вместо ее вызова. Это потому, что мы хотим вернуть здесь значение из div () в wrapper () в декоратор ().
а. Замыкание Python
Когда мы вызываем func, он запоминает значение func из аргумента функции decorator (). В Python это называется закрытием. Вот еще один пример, чтобы прояснить это.
>>> msg = "Привет" >>> def func1 (сообщение): def func2 (): печать (сообщение) func2 () >>> func1 (сообщение)
Привет
Также обратите внимание, что если бы мы вызвали func (a, b) вместо того, чтобы возвращать его, мы получили бы это:
>>> разделить (2,3) >>> print (разделите (2,3))
Нет
А теперь давайте назначим и позвоним.
>>> разделить = декоратор (разделить) >>> разделить (2,3)
0,6666666666666666
>>> разделить (2,0)
Нельзя делить на 0!
Проблема решена.
г. * аргументы и ** kwargs
Если вы не хотите вводить весь список аргументов для двух операторов, * args и ** kwargs сделают это за вас.
>>> def div (a, b): вернуть а / б >>> def decorate (func): def оболочка (* args, ** kwargs): если args [1] == 0: print («Нельзя делить на 0!») возвращение return func (* args, ** kwargs) возвратная обертка >>> разделить = украсить (разделить) >>> разделить (2,0)
Нельзя делить на 0!
Видите, работает.Фактически, * args — это кортеж аргументов, а ** kwargs — словарь аргументов ключевого слова.
Есть ли сомнения в декораторах Python 3? Спрашивайте в комментариях.
6. Синтаксис пирога в Python
Сначала выполните синтаксис Python | Лучший учебник по синтаксису Python
Если вы считаете, что операторы присваивания и вызова не нужны, у нас есть для вас синтаксис круговой диаграммы. Это просто; назовите функцию декорирования после символа @ и поместите это перед функцией для украшения.Вот пример.
>>> @decor def sayhello (): print ("Привет") >>> sayhello ()
$$
Привет
$$
7. Создание цепочек декораторов в Python
Вам не нужно ограничиваться только одним декоратором Python. В этом вся прелесть синтаксиса пирога. Посмотрим как.
>>> def decor1 (func): def wrap (): print ("$$$$$$$$$$$$$$") func () print ("$$$$$$$$$$$$$$") возвратная упаковка >>> def decor2 (func): def wrap (): Распечатать("##############") func () Распечатать("##############") возвратная пленка
Теперь давайте определим функцию sayhello ().Мы будем использовать здесь decor1 и decor2.
>>> @ decor1 @ decor2 def sayhello (): print ("Привет") >>> sayhello ()
$$$$$$$$$$$$$$
########
Привет
########
$$$$$$$$$$$$$$
Обратите внимание на то, как октторпы (#) зажаты между долларами ($)? Это дает нам важную информацию — порядок декораторов в python в синтаксисе pie имеет значение. Поскольку сначала мы использовали decor1, мы первыми получаем доллары.
Это было все о Python Decorator Tutorial.
8. Заключение
Завершая то, что мы обсуждали до сих пор в этом Python Decorator с аргументами, простые примеры Учебное пособие, декораторы в python помогают нам добавлять дополнительные функции к функции без ее изменения. Мы также видели синтаксис пирога для того же самого. И теперь вы знаете — все, что вас смущает, вы можете победить, столкнувшись с этим, потому что вы не можете бежать вечно. Побег нереален. Увидимся снова.
См. Также:
.