Питон декоратор в классе

Руководство по декораторам Python

Декораторы — одна из самых классных особенностей Python. Декоратор принимает функцию, добавляет новые возможности и возвращает улучшенный вариант. Разберемся с тем, как это работает.

В Python все является объектом. И функции — не исключение. Поскольку они тоже являются объектами, их можно передавать в качестве аргументов другим функциям и возвращать в качестве результата. У них также есть атрибуты: __name__ и __doc__ .

Функция — это группа инструкций, которые выполняют определенную задачу. Задача функции — организовать код, избежать повторений и заново использовать отдельные блоки.

def function_name(args): '''docstring''' statements(s)

Синтаксис функций очень простой. Она начинается с ключевого слова def . Дальше идет уникальное имя, параметры и двоеточие. Также может быть документация (docstring), которая описывает назначение функции.

Все инструкции, которые идут дальше, составляют тело функции. Они должны иметь корректные отступы. В конце также может быть инструкция return .

 
def basic_function(a):
'''Базовая функция'''
print('Базовая функция:', a)
return a + 1

print('старт программы')
print('имя:', basic_function.__name__)
print('док:', basic_function.__doc__)
b = basic_function(1)
print('конец программы:', b)
старт программы имя: basic_function док: Базовая функция Базовая функция: 1 конец программы: 2

Функция — это вызываемый объект. Как можно догадаться, это подразумевает объект, который можно вызвать. Проверить эту особенность можно с помощью встроенной функции callable() .

Декораторы

Итак, декоратор — это функция, которая принимает функцию, делает что-то и возвращает другую функцию.

 
def decorator(func):
def wrapper():
print('функция-оболочка')
func()
return wrapper

def basic():
print('основная функция')

wrapped = decorator(basic)
print('старт программы')
basic()
wrapped()
print('конец программы')

Если запустить эту программу:

старт программы основная функция функция-оболочка основная функция конец программы

Разберемся с тем, что здесь произошло. Функция decorator — это, как можно понять по названию, декоратор. Она принимает в качестве параметра функцию func . Крайне оригинальные имена. Внутри функции объявляется другая под названием wrapper . Объявлять ее внутри необязательно, но так проще работать.

В этом примере функция wrapper просто вызывает оригинальную функцию, которая была передана в декоратор в качестве аргумента, но это может быть любая другая функциональность.

В конце возвращается функция wrapper . Напомним, что нам все еще нужен вызываемый объект. Теперь результат можно вызывать с оригинальным набором возможностей, а также новым включенным кодом.

Но в Python есть синтаксис для упрощения такого объявления. Чтобы декорировать функции, используется символ @ рядом с именем декоратора. Он размещается над функцией, которую требуется декорировать.

 
def decorator(func):
'''Основная функция'''
print('декоратор')
def wrapper():
print('-- до функции', func.__name__)
func()
print('-- после функции', func.__name__)
return wrapper @decorator
def wrapped():
print('--- обернутая функция') print('- старт программы. ')
wrapped()
print('- конец программы')
декоратор - старт программы. -- до функции wrapped --- обернутая функция -- после функции wrapped - конец программы

Можно использовать тот же декоратор с любым количеством функций, а также — декорировать функцию любым декоратором.

 
def decorator_1(func):
print('декоратор 1')
def wrapper():
print('перед функцией')
func()
return wrapper

def decorator_2(func):
print('декоратор 2')
def wrapper():
print('перед функцией')
func()
return wrapper

@decorator_1
@decorator_2
def basic_1():
print('basic_1')

@decorator_1
def basic_2():
print('basic_2')

print('>> старт')
basic_1()
basic_2()
print('>> конец')
декоратор 2 декоратор 1 декоратор 1 >> старт перед функцией перед функцией basic_1 перед функцией basic_2 >> конец

Когда у функции несколько декораторов, они вызываются в обратном порядке относительно того, как были вызваны. То есть, такой вызов:

@decorator_1 @decorator_2 def wrapped():
a = decorator_1(decorator_2(wrapped))

Если декорированная функция возвращает значение, и его нужно сохранить, то нужно сделать так, чтобы его возвращала и функция-обертка.

Декоратор-класс

Добавив метод __call__ в класс, его можно превратить в вызываемый объект. А поскольку декоратор — это всего лишь функция, то есть, вызываемый объект, класс можно превратить в декоратор с помощью функции __call__ .

Лучше всего разобрать это на примере.

 
class Decorator:
def __init__(self, func):
print('> Класс Decorator метод __init__')
self.func = func

def __call__(self):
print('> перед вызовом класса. ', self.func.__name__)
self.func()
print('> после вызова класса')

@Decorator
def wrapped():
print('функция wrapped')

print('>> старт')
wrapped()
print('>> конец')
> Класс Decorator метод __init__ >> старт > перед вызовом класса. wrapped функция wrapped > после вызова класса >> конец

Отличие в том, что класс инициализируется при объявлении. Он должен получить функцию в качестве аргумента для метода __init__ . Это и будет декорируемая функция.

При вызове декорируемой функции на самом деле вызывается экземпляр класса. А поскольку объект вызываемый, то вызывается функция __call__ .

Функция с аргументами

А что если функция, которую требуется декорировать, должна получать аргументы? Для этого нужно вернуть функцию с той же сигнатурой, что и у декорируемой.

 
def decorator_with_args(func):
print('> декоратор с аргументами. ')
def decorated(a, b):
print('до вызова функции', func.__name__)
ret = func(a, b)
print('после вызова функции', func.__name__)
return ret
return decorated

@decorator_with_args
def add(a, b):
print('функция 1')
return a + b

@decorator_with_args
def sub(a, b):
print('функция 2')
return a - b

print('>> старт')
r = add(10, 5)
print('r:', r)
g = sub(10, 5)
print('g:', g)
print('>> конец')
> декоратор с аргументами. > декоратор с аргументами. >> старт до вызова функции add функция 1 после вызова функции add r: 15 до вызова функции sub функция 2 после вызова функции sub g: 5 >> конец

А в случае с классом? Тот же принцип. Нужно лишь добавить желаемую сигнатуру в функцию __call__ .

 
class Decorator:
def __init__(self, func):
print('> Класс Decorator метод __init__')
self.func = func

def __call__(self, a, b):
print('> до вызова из класса. ', self.func.__name__)
self.func(a, b)
print('> после вызова из класса')

@Decorator
def wrapped(a, b):
print('функция wrapped:', a, b)

print('>> старт')
wrapped(10, 20)
print('>> конец')
> Класс Decorator метод __init__ >> старт > до вызова из класса. wrapped функция wrapped: 10 20 > после вызова из класса >> конец

Можно использовать *args и **kwargs и для функции wrapper, если сигнатура заранее неизвестна, или будут приниматься разные типы функций.

Декораторы с аргументами

В декоратор можно передать и сам параметр. В этом случае нужно добавить еще один слой абстракции, то есть — еще одну функцию-обертку.

Это обязательно, поскольку аргумент передается декоратору. Затем функция, которая вернулась, используется для декорации нужной. Проще разобраться на примере.

 
def decorator_with_args(name):
print('> decorator_with_args:', name)
def real_decorator(func):
print('>> сам декоратор', func.__name__)
def decorated(*args, **kwargs):
print('>>> перед функцие', func.__name__)
ret = func(*args, **kwargs)
print('>>> после функции', func.__name__)
return ret
return decorated
return real_decorator

@decorator_with_args('test')
def add(a, b):
print('>>>> функция add')
return a + b

print('старт программы')
r = add(10, 10)
print(r)
print('конец программы')
> decorator_with_args: test >> сам декоратор add старт программы >>> перед функцие add >>>> функция add >>> после функции add 20 конец программы

В декораторах-классах выполняются такие же настройки. Теперь конструктор класса получает все аргументы декоратора. Метод __call__ должен возвращать функцию-обертку, которая, по сути, будет выполнять декорируемую функцию. Например:

 
class DecoratorArgs:
def __init__(self, name):
print('> Декоратор с аргументами __init__:', name)
self.name = name

def __call__(self, func):
def wrapper(a, b):
print('>>> до обернутой функции')
func(a, b)
print('>>> после обернутой функции')
return wrapper

@DecoratorArgs("teste")
def add(a, b):
print('функция add:', a, b)

print('>> старт')
add(10, 20)
print('>> конец')
> Декоратор с аргументами __init__: teste >> старт >>> до обернутой функции функция add: 10 20 >>> после обернутой функции >> конец

Документация

Один из атрибутов функции — строка документации (docstring), доступ к которой можно получить с помощью __doc__ . Это строковая константа, определяемая как первая инструкция в объявлении функции.

При декорации возвращается новая функция с другими атрибутами. Но они не изменяются.

 
def decorator(func):
'''Декоратор'''
def decorated():
'''Функция Decorated'''
func()
return decorated

@decorator
def wrapped():
'''Оборачиваемая функция'''
print('функция wrapped')

print('старт программы. ')
print(wrapped.__name__)
print(wrapped.__doc__)
print('конец программы')

В этом примере функция wrapped — это, по сути, функция decorated , которую она заменяет.

старт программы. decorated Функция Decorated конец программы

Вот где на помощь приходит функция wraps из модуля functools . Она сохраняет атрибуты оригинальной функции. Нужно лишь декорировать функцию wrapper с ее помощью.

 
from functools import wraps

def decorator(func):
'''Декоратор'''
@wraps(func)
def decorated():
'''Функция Decorated'''
func()
return decorated

@decorator
def wrapped():
'''Оборачиваемая функция'''
print('функция wrapped')

print('старт программы. ')
print(wrapped.__name__)
print(wrapped.__doc__)
print('конец программы')
старт программы. wrapped Оборачиваемая функция конец программы

Приложения

До этого момента мы не касались того, как декораторы используются в реальных приложения. Поэтому перейдем к примерам.

Таймеры

Базовая функциональность — время работы функции. Есть возможность получить время до и после вызова функции, использовав полученный результат (для записи в лог, базу данных, для отладки и так далее).

 
from datetime import datetime
import time

def elapsed(func):
def wrapper(a, b, delay=0):
start = datetime.now()
func(a, b, delay)
end = datetime.now()
elapsed = (end - start).total_seconds() * 1000
print(f'>> функция время выполнения (ms): ')
return wrapper

@elapsed
def add_with_delay(a, b, delay=0):
print('сложить', a, b, delay)
time.sleep(delay)
return a + b

print('старт программы')
add_with_delay(10, 20)
add_with_delay(10, 20, 1)
print('конец программы')
старт программы сложить 10 20 0 >> функция add_with_delay время выполнения (ms): 36.006 сложить 10 20 1 >> функция add_with_delay время выполнения (ms): 1031.255 конец программы

Логи

Еще один распространенный сценарий применения для декоратора — логирование функций.

 
import logging

def logger(func):
log = logging.getLogger(__name__)
def wrapper(a, b):
log.info("Вызов функции ", func.__name__)
ret = func(a, b)
log.info("Вызвана функция ", func.__name__)
return ret
return wrapper

@logger
def add(a, b):
print('a + b:', a + b)
return a + b

print('>> старт')
add(10, 20)
add(20, 30)
print('>> конец')

Функция обратного вызова

Функция обратного вызова — это функция, которая вызывается при срабатывании определенного события (переходе на страницу, получении сообщения или окончании обработки процессором).

Можно передать функцию, чтобы она выполнилась после определенного события. Это используется, например, в HTTP-серверах в ответ на URL-запросы.

Источник

Читайте также:  Java классы без имени
Оцените статью