Декораторы функций в Python

Для понимания декораторов нужно помнить что, функция в Python это объект и функция может быть определена в другой функции. Так же нужно понимания Области видимости и замыкания в Python.
Декоратор функций в общем виде это функция которая принимает функцию и возвращает функцию. Так же возможна реализация через класс и реализацию метода __call__
Главное свойство декораторов -то, что они выполняются сразу после определения декорируемой функции.

registry = []


def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func


@register
def f1():
    print('running f1()')


def f2():
    print('running f2()')
f2 = register(f2)   # эквивалентно @register


def f3():
    print('running f3()')


def main():
    print('running main()')
    print('registry -> ', registry)
    f1()
    f2()
    f3()


if __name__ == '__main__':
    main()

Функция register добавляет декорированные функции в список.
@register эквивалентно f2 = register(f2)
Соответственно декоратор вызывается при загрузки модуля.

Композиции декораторов

@d1
@d2
def f():
    print('f')

Когда два декоратора d1 и d2 применяются к одной функции f в указанном порядке, получается то же самое, что в результате композиции f = d1(d2(f))

Большинство декораторов создают новую функцию с дополнительной логикой и вызывают в ней декорированную функцию.

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

import time


def clock(func):
    """
    Фактически функция clocked заменит собой декорированую функцию.
    При вызове clocked вызывает декорированую функцию и возращает результат этой функции.
    При этом поевляется возможность добавить дополнительную логику
        до и/или полсе вызова декорированной функции.
    """
    def clocked(*args, **kwargs):
        t0 = time.time()

        result = func(*args, **kwargs)  # вызов декорированной функции

        elapsed = time.time() - t0
        name = func.__name__
        arg_1st = []
        if args:
            arg_1st.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_1st.append(', '.join(pairs))
        arg_str = ', '.join(arg_1st)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result
    return clocked


@clock
def factorial(n):
    if n < 2:
        return n
    return factorial(n-2) + factorial(n-1)
# factorial = clock(factorial)

print('10! =', factorial(10))  # фактически вызываем clocked(100)

Параметризованный декоратор

Для реализации параметризованных декораторов нужна фабрика декораторов, которая принимает параметры, объявляет функцию декоратор и возвращает ее как результат.
Используя замыкания у декоратора будет доступ к параметрам переданным фабрике.

Добавим возможность передавать строку формата вывода для clock

import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name} ({arg_str}) -> {result}'


def clock(fmt=DEFAULT_FMT):

    def decorate(func):
        def clocked(*args, **kwargs):
            t0 = time.time()
            result = func(*args, **kwargs)
            elapsed = time.time() - t0
            name = func.__name__
            arg_1st = []
            if args:
                arg_1st.append(', '.join(repr(arg) for arg in args))
            if kwargs:
                pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
                arg_1st.append(', '.join(pairs))
            arg_str = ', '.join(arg_1st)
            print(fmt.format(**locals()))
            return result
        return clocked

    return decorate


@clock()
def factorial(n):
    if n < 2:
        return n
    return factorial(n-2) + factorial(n-1)


def factorial2(n):
    if n < 2:
        return n
    return factorial2(n-2) + factorial2(n-1)

factorial2 = clock('{name}: {elapsed}s')(factorial2)

print('6! =', factorial(6))

print('6! =', factorial2(6))

Опять же это можно записать через стандартные вызовы функций
factorial2 = clock('{name}: {elapsed}s')(factorial2)


Еще один пример. Примитивная реализация стандартного route декоратора для web framework
route_map = {}


def route(path):
    def decorate(func):
        route_map[path] = func
        return func

    return decorate


@route('/users')
def users():
    return 'all users'


@route('/shops')
def shops():
    return 'all shops'


from urllib.parse import urlparse

url = urlparse('http://example.com/users')
handler = route_map.get(url.path)
if handler:
    print(handler())

url = urlparse('http://example.com/shops')
handler = route_map.get(url.path)
if handler:
    print(handler())


Комментарии

Популярные сообщения из этого блога

Асинхронное выполнение процедур или триггера в MS SQL Server

Рекурсивные SQL запросы

Кратко про SQLAlchemy Core