Python: Декораторы
Всё, с чем мы работаем в Python — это объекты в терминах ООП: переменные, классы, экземпляры классов и даже импортированные модули. И функции — не исключение.
Запустите в консоли интерактивный режим Python, нам надо поэкспериментировать.
Выполните такой код:
Скопировать кодPYTHON
def func(x, y):
return x+y
print(type(func))
При создании нового объекта он сохраняется в памяти по определённому адресу, а имя добавляется в доступное пространство имен и указывает на этот адрес.
Скопировать кодPYTHON
hex(id(func))
Создадим переменную new_func и присвоим ей значение func, точно так же, как присвоили бы переменной строковый или числовой объект:
Скопировать кодPYTHON
new_func = func
print(new_func(1, 2))
hex(id(new_func))
Значение первой переменной можно заменить, но вторая переменная всё равно будет указывать на прежний объект.
Скопировать кодPYTHON
func = 'just a string'
print(func)
hex(id(func))
hex(id(new_func))
Старую переменную func мы переопределили, но new_func продолжает указывать на ту же самую функцию, на которую изначально указывало имя func.
Поскольку функция ведет себя как обычная переменная, то ее можно передавать в качестве параметра в другую функцию:
Скопировать кодPYTHON
def apply(f, x, y):
return f(x, y)
print(apply(new_func, 1, 2))
Если функцию можно передать, то что мешает вернуть функцию?
Скопировать кодPYTHON
def operation(name):
def add(x, y):
return x + y
def mul(x, y):
return x * y
if name == 'add':
return add
if name == 'mul':
return mul
op = operation('add')
print(op(1, 3))
op2 = operation('mul')
print(op2(2, 5))
print(operation('mul')(2, 5))
Внутри функции мы создали другие функции, а потом внешним переменным присвоили ссылки на эти «внутренние» функции.
Можно сделать еще один шаг: обернуть входящую функцию нужным кодом прежде, чем её исполнить.
- Создадим функцию
wrapper() - Передадим ей как параметр функцию
some_func() - Функция
wrapper() передаёт some_func() в свою внутреннюю функцию added_value(), которая выполняет некие полезные действия - Функция
wrapper() возвращает функцию added_value()
Скопировать кодPYTHON
def wrapper(func):
_cache = {'counter': 0}
def added_value():
_cache['counter'] = _cache['counter'] + 1
print("Полезная работа до начала работы функции")
func()
print("Полезная работа после выполнения функции")
return added_value
def some_func():
print("Я полезная функция")
do = wrapper(some_func)
do()
Можно упростить вызов, не создавая промежуточных переменных, а заменить some_func на саму себя, «обернутую» в функцию wrapper():
Скопировать кодPYTHON
some_func = wrapper(some_func)
some_func()
Теперь при каждом обращении к функции some_func() будет выполняться внутренняя функция added_value() из функции wrapper()
Код, размещённый в декораторе вне внутренней функции, будет выполнен лишь единожды. В декораторе wrapper() будет создана переменная _cache и значение _cache['counter'] будет увеличиваться на единицу с каждым вызовом декоратора, но не будет создаваться каждый раз заново.
Задача по изменению работы функций достаточно распространена. Например, можно «обернуть» функцию и измерить время ее выполнения. Или зарегистрировать функцию в реестре, чтобы потом обращаться к ней через этот реестр: мы делали так при создании странного фильтра uglify.
Можно одной и той же «обёрткой» настроить разные функции так, чтобы они выполнялись только один раз, сохраняли значение и при повторном обращении моментально отдавали готовый результат.
Такое решение оказалось столь популярно и удобно, что в языке была добавлена специальная конструкция: декоратор.
Если перед определением функции написать имя «оборачивающей» функции через знак @, то определяемая функция будет передаваться в «обёртку» и возвращать результат через неё.
Скопировать кодPYTHON
@wrapper # оборачиваем some_func() в декоратор wrapper()
def some_func():
print("Я полезная функция")
Упрощённый синтаксис, «синтаксический сахар», существует только для того, чтобы делать работу программиста удобнее.
Работа с аргументами
Функция-декоратор получает на вход только один параметр — декорируемую функцию, а возвращает внутреннюю функцию-исполнитель. Параметры декорируемой функции можно передать в функцию-исполнитель.
Декораторы обычно пишут универсальными: они должны принимать на вход функции с любым количеством и типом параметров. Для этого есть конструкции *args и **kwargs, это маски для получения любого количества позиционных (*args от arguments) и именованных (**kwargs от keyword arguments) аргументов. Эти конструкции могут применяться в любых функциях (не только в декораторах), имена arg и kwarg не предустановлены, но общеприняты.
В теле функции с переменной *args можно работать как с кортежем, а с переменной **kwargs — как со словарём.
Скопировать кодPYTHON
def wrapper(func):
def added_value(*args, **kwargs):
print("Полезная работа до начала работы функции")
func(*args, **kwargs)
print (args)
print (kwargs)
print("Полезная работа после выполнени функции")
return added_value
@wrapper # декоратор
def some_func():
Теперь любая декорируемая функция будет вызвана без ошибок.