Python-декораторы - это что такое?
Декоратор - шаблон, предназначенный для подключения дополнительного поведения к объекту. Используется во многих языках ООП: Java, C#, PHP, JS. Python - не исключение.
Задачу декораторов можно описать на следующем примере. Есть объект, который выполняет некоторую функцию, в ходе разработки программы требуется добавить к нему некоторую дополнительную функциональность. Она может выполняться до или после, или даже во время основных функций. Для решения этой задачи используются декораторы. Они расширяют функциональность, избавляют от создания второго такого же класса с дополнительными методами.
Декораторы Python - это своеобразная обертка, которая изменяет поведение функции. В качестве изменяемого объекта используется класс, функция или другой декоратор. Они должны применяться очень осторожно с четким пониманием того, чего именно нужно добиться. Слишком частое использование декораторов приводит к усложнению понимания кода.
Декоратор и функция
Декораторы в Python - это функция, которая использует другую функцию как аргумент. Представляет собой блок кода, который возвращает некоторое значение.
Содержит аргументы, они будут использоваться в дальнейшем и повлияют на возвращаемое значение. Результат, который получается на выходе, может быть любого типа: список, кортеж, функция.
В "Пайтоне" каждая функция - объект, объявляется он с помощью ключевого слова def. Область значений задается не фигурными скобками, а обозначается отступом tab. После ключевого слова указывается имя, аргументы задаются в скобках () после имени. Перед переходом на новую строку ставится символ “:”.
В "Пайтоне" тело не может быть пустым, должно обязательно содержать список команд. Если нужно оставить это место ставится пустой оператор pass.
def empty_func():
pass
Подобный синтаксис распространяется на все функции, кроме анонимной. Анонимная выглядит так:
func = lambda x, y: x + y
Вызов:
func(1, 2,) #возвращает 3
Вызов (второй способ):
(lambda x, y: x + y)(1, 2) #возвращает 3
Декораторы вызываются так:
@имя декоратора
def изменяемая_функция
тело изменяемой_функции
Схема работы описывается следующим кодом:
def decorator(change_funct):
def return_func1():
print “code before”
change_funct()
print “code after”
return return_func1
Соответственно, вызов происходит следующим образом:
@decorator
def retrurn_func1():
print new_change
Аргументы функции
В аргументах декораторов Python передается любой тип данных.
Переменные одного типа перечисляются через запятую. Есть несколько способов того, как назначаются значения переменным, указанные в параметрах.
- Обычный.
- С помощью ключевых слов.
- Задание статических значений.
- Использование позиционных элементов.
При создании указывается несколько аргументов в определенном порядке. При вызове в параметрах указываются все значения в соответствующем порядке.
def bigger(a,b):
if a > b:
print a
else:
print b
Правильный вызов:
bigger(5,6)
Неправильный вызов:
bigger(3)
bigger(12,7,3)
Если используемые аргументы являются ключевыми словами, их вызов осуществляется в произвольном порядке, так как используемое значение определяет конкретное имя-ключ.
def person(name, age):
print name, "is", age, "years old"
person(age=23, name="John")
Статические значения переменных создаются вместе с функцией через оператор присваивания так, как если бы инициализация происходила в теле.
def space(planet_name, center="Star"):
print(planet_name, "is orbiting a", center)
space("Mars")
Когда количество аргументов на этапе создания функции неизвестно, используются позиционные аргументы. Они могут обозначать несколько переменных одного типа или список:
def func(*args):
return args
func(1, 2, 3, 'abc')
# (1, 2, 3, 'abc')
func(1)
#(1,)
Похожим способом передаются библиотеки значений с ключами - с помощью символа “**”.
Переменные, указанные в теле, являются локальными, используются непосредственно самой функцией. Для создания глобальной переменной применяется спецификатор global.
def get_older():
global age
age += 1
Поддерживается рекурсия.
def fact(num):
if num == 0:
return 1
else:
return num * fact(num - 1)
Декорирование методов
Функции и методы синтаксически похожи.
Разница в том, что функция вызывается только по имени.
func()
А вызов метода осуществляется через оператор “.” и вводится имя метода, где первым параметром является родитель.
object.func()
Таким образом, декораторы Python для методов создаются так же, как для функции.
Здесь мы создали декоратор def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 #
return method_to_decorate(self, lie)
return wrapper
F здесь создали класс с методами, которые позже будут модифицированы^
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def sayYourAge(self, lie):
print "Мне %s, а ты бы сколько дал?" % (self.age + lie)
Lucy.sayYourAge(-32)
#Мне 26, а ты бы сколько дал?
Здесь использован format(). Предназначается для форматирование строк, используется в таком виде:
print(“string {} {}”).format(1, 2)
#string 1 2
В этом примере в аргументах format() указаны две цифры: 1, 2. Они замещают символы {} в таком порядке, в котором расположены. Форматирование доступно исключительно для строковых элементов. То есть, аргумент становится на место первых фигурных скобок {}, а второй - вторых, соответственно. В методе format предусмотрена возможность менять порядок вставки значений. Это делается через индексы.
Если:
print(“string {} {}”).format(1, 2)
#string 1 2
То:
print(“string {1} {0}”).format(1, 2)
#string 2 1
Допускается форматирование строк через ключевые имена в формате format(arg1 = value1, arg2 = value2).
print(“string {arg1} {arg2}”).format(arg1 = 1, arg2 = 2)
#string 1 2
Можно использовать смешанную систему - когда при двух аргументах только один из них имеет статическое значение. Для передачи значений указывается индекс и имя переменной.
print(“string {arg1} {1}”).format(arg1 = 1, 2)
#string 1 2
Декораторы с аргументами
Декораторам Python можно передать аргументы, которые впоследствии модифицируют обрабатываемую функцию.
def decorator(function_to_decorate):
def function(arg1, arg2):
print "Смотри, что я получил:", arg1, arg2
return function
В данном случае есть @decorator, который модифицирует function. Аргументы пробрасываются во второй строке, передаются изменяемой function_to_decorate.
Вызов:
@decorator
def real_func(Петр, Иванович)
print “Меня зовут”, arg1, arg2
На экране появится:
Смотри, что я получил: Петр, Иванович
Меня зовут Петр Иванович
Вложенные декораторы
Когда не достаточно одного декоратора, реализовывается несколько уровней обертывания. При создании вложенного декоратора каждый начинается с новой строки, количество строк определяет уровень сложности. Выглядит так:
@AAA
@BBB
@CCC
def function(...):
pass:
Соответственно, AAA(), принимает в параметрах BBB(), а она, обрабатывает CCC().
def f():
pass:
f = (AAA(BBB(CCC(function))):
Function передается трем различным декоратором, присваивается f(). Каждый из них возвращает свой результат, который, в свою очередь, обрабатывает обертку. Можно заметить, что последний декоратор списка является первым, он начинает обрабатывать function().
В Python декораторы класса выглядят также.
@firsdecorator
@seconddecorator
class CDC:
pass:
C = firstdecorator(seconddecorator(CDC))
XX = C()
def fn1(arg): return lambda: 'XX' + arg()
def fn2(arg): return lambda: 'YY' + arg()
def fn3(arg): return lambda: 'ZZ' + arg()
@fn1
@fn2
@fn3
def myfunc(): # myfunc = fn1(fn2(fn3(myfunc)))
return 'Python'
print(myfunc()) # Выведет "XXYYZZPython"
В данном случае реализация обертывающей логики происходит путем использования def lambda:
lambda: 'XX' + arg()
Fn3() оборачивает myfunc, возвращает ZZPython вместо предыдущей строки Python. Затем начинает работать оборачиваемая fn2(), которая в итоге возвращает результат YYZZPython. В конце fn1() обрабатывает myfunc() и возвращает конечный результат - строку XXYYZZPython.
Встроенные декораторы
Существуют встроенные декораторы функций Python. Они поставляются в комплекте с интерпретатором, для их использования требуется импортировать дополнительные модули.
Staticmethod обрабатывает функцию-аргумент так, что она становится статической и принимает спецификатор static.
class C:
@staticmethod
def f(arg1, arg2, ...): #static
pass
Classmethod делает из обрабатываемой функции класс.
class MyClass:
@classmethod
def method(cls, arg):
print('%s classmethod. %d' % (cls.__name__, arg))
@classmethod
def call_original_method(cls):
cls.method(5)
def call_class_method(self):
self.method(10)
class MySubclass(MyClass):
@classmethod
def call_original_method(cls):
cls.method(6)
MyClass.method(0) # MyClass classmethod. 0
MyClass.call_original_method() # MyClass classmethod. 5
MySubclass.method(0) # MySubclass classmethod. 0
MySubclass.call_original_method() # MySubclass classmethod. 6
# Вызываем методы класса через объект.
my_obj = MyClass()
my_obj.method(1)
my_obj.call_class_method()
Любая функция может использоваться как декоратор.
Замена сеттеров и геттеров
С помощью класса property назначаются геттеры, сеттеры, делеттеры.
class property([fget[, fset[, fdel[, doc]]]])
Геттеры и сеттеры в TypeScript представлены в таком виде:
Осуществляется передача параметров в класс Python. Декоратор property имеет методы:
- fget - получает значение атрибута;
- fset определяет значение атрибута;
- fdel удаляет;
- doc создает описание для атрибута. Если doc не назначен, возвращается копия описание fget(), если имеется.
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
Использование функции как декоратор Pythonproperty:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Property создал из функции x декоратор. Поскольку у всех декораторов есть встроенные методы setter, getter, deletter, можно вызвать один из них.
Особенности
При работе с декоратором необходимо учитывать некоторые особенности:
- Использование декораторов немного замедляет вызов функции.
- Единожды задекорированная функция не может быть раздекорированной. Существуют способы обхода этого правила. Можно создать такой декоратор, который можно будет впоследствии отсоединить от функции. Но это не очень удачная практика.
- Из-за того, что декоратор оборачивает функцию, может осложниться отладка. Проблема решается с помощью модуля functools.
Модуль functools - сборник методов, благодаря которым обеспечивается взаимодействие с другими функциями, он также является декоратором Python.
Полезный метод cmp_to_key(func) превращает cmp() в key(). Оба метода предназначены для сортировки списка, но первый удален в "Пайтон 3.0", а второй добавлен в версии 2. Lru_cache сохраняет последние вызовы в кэш. Если maxsixe указан как none, размер кэша возрастает бесконечно. Для хранения часто используемых запросов используется словарь. Если аргумент typed=true, то аргументы разных типов кэшируется отдельно. Соответственно, при typed=true, сохраняются в единый список.
Total_ordering декорируют класс, который содержит методы сравнения, и добавляет все остальные.
Partial(func, *args, **keywords) возвращает функцию, которая вызывается по первому аргументу из области параметров метода, передает позиционный *args, который задается вторым, и именованный kwargs.
Reduce работает так:
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
Эквивалентно:
((((1+2)+3)+4)+5)
Reduce применяет указанную функцию последовательно к парам элементов, указанных в списке **keywords, или ко всем элементам *args. Таким образом, в примере выше с помощью lambda function обрабатываются первые два элемента:
1+2
Далее результат суммируется с третьим, полученный из этого результат прибавляется к следующими элементам и т.д.
Update_wrapper обновляет оболочку так, чтобы она напоминала обернутую функцию. В аргументах указываются эти две функции, копируемые и обновляемые атрибуты.
assigned=WRAPPER_ASSIGNMENTS
Кортеж WRAPPER_ASSIGNMENTS содержит по умолчанию значения __name__, __module__, __annotations__ и __doc__.
updated=WRAPPER_UPDATES
В WRAPPER_UPDATES указываются атрибуты, которые обновляются, по умолчанию это __dict__.
Wraps вызывает partial в качестве декоратора.
Декоратор обработки ошибки
Возможности декораторов позволяют создавать такую функцию, которая при возникновении ошибки выдает один результат, если ошибки нет - другой.
Реализация:
import functools def retry(func): @functools.wraps(func) def wrapper(*args, **kwargs): while True: try: return func(*args, **kwargs) except Exception: pass return wrapper
Видно, что в случае исключений функция запускается заново.
@retry def do_something_unreliable(): if random.randint(0, 10) > 1: raise IOError("Broken sauce, everything is hosed!!!111one") else: return "Awesome sauce!" print(do_something_unreliable())
Этот код означает, что 9 из 11 случаев возникнет ошибка.
def my_decorator(fn): def wrapped(): try: return fn() except Exception as e: print("Error:", e) return wrapped @my_decorator def my_func(): import random while True: if random.randint(0, 4) == 0: raise Exception('Random!') print('Ok') my_func()
Здесь в консоль выведется:
Ok Ok Ok Error: Random!
Создание своего декоратора
Декоратор представляет собой функцию, внутри которой находится другая функция с соответствующими методами.
Пример декоратора в Python:
def my_shiny_new_decorator(function_to_decorate):
def the_wrapper_around_the_original_function():
print("Я - код, который отработает до вызова функции")
function_to_decorate()
print("А я - код, срабатывающий после")
return the_wrapper_around_the_original_function
В этом коде декоратором является my_shiny_new_decorator(). В дальнейшем декорирует function_to_decorate() из области параметров. the_wrapper_around_the_original_function - это пример того, как будет обработана декорируемая функция. В данном случае добавляется:
print("Я - код, который отработает до вызова функции")
print("А я - код, срабатывающий после")
My_shiny_new_decorator() возвращает декорируемую the_wrapper_around_the_original_function().
Чтобы отработать какую-либо функцию, она оборачивается декоратором.
stand_alone_function = my_shiny_new_decorator(stand_alone_function)
В данном случае декорированная функция - stand_alone_function, декоратор - это my_shiny_new_decorator. Значение присваивается переменной stand_alone_function.
def stand_alone_function():
print("Я простая одинокая функция, ты ведь не посмеешь меня изменять?")
stand_alone_function()
Я - код, который отработает до вызова функции
Я простая одинокая функция, ты ведь не посмеешь меня изменять?
А я - код, срабатывающий после
Таким образом, видно, что stand_alone_function, которая выводила на экран одно предложение, теперь выводит три предложения. Это осуществляется с помощью декоратора.