Декораторы функций в Python
Для понимания декораторов нужно помнить что, функция в Python это объект и функция может быть определена в другой функции. Так же нужно понимания Области видимости и замыкания в Python.
Декоратор функций в общем виде это функция которая принимает функцию и возвращает функцию. Так же возможна реализация через класс и реализацию метода __call__
Главное свойство декораторов -то, что они выполняются сразу после определения декорируемой функции.
Функция register добавляет декорированные функции в список.
@register эквивалентно f2 = register(f2)
Соответственно декоратор вызывается при загрузки модуля.
Когда два декоратора d1 и d2 применяются к одной функции f в указанном порядке, получается то же самое, что в результате композиции f = d1(d2(f))
Большинство декораторов создают новую функцию с дополнительной логикой и вызывают в ней декорированную функцию.
Классический декоратор принимающий функцию с произвольным числом переменных и добавляющий логику расчета затраченного времени.
Используя замыкания у декоратора будет доступ к параметрам переданным фабрике.Декоратор функций в общем виде это функция которая принимает функцию и возвращает функцию. Так же возможна реализация через класс и реализацию метода __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())
Комментарии
Отправить комментарий