Эти декораторы Python позволят сократить код вдвое.
Декораторы Python позволяют изменять поведение функции или класса, не меняя при этом исходный код. Это функции, принимающие другую функцию в качестве аргумента и возвращающие новую функцию, которая оборачивает (wrap) исходную. Таким образом, не меняя код исходной функции, можно добавлять к ней определенную дополнительную функциональность или логику.
Например, есть функция вывода сообщения на консоль: def hello(): print("Hello, world!")
Предположим, ?
Эти декораторы Python позволят сократить код вдвое...
Декораторы Python позволяют изменять поведение функции или класса, не меняя при этом исходный код. Это функции, принимающие другую функцию в качестве аргумента и возвращающие новую функцию, которая оборачивает (wrap) исходную. Таким образом, не меняя код исходной функции, можно добавлять к ней определенную дополнительную функциональность или логику.
Например, есть функция вывода сообщения на консоль:
def hello(): print("Hello, world!")
Предположим, нужно измерить длительность исполнения этой функции. Для этого можно написать другую функцию с модулем time
, вычисляющую время выполнения, а затем вызывающую исходную функцию:
import time def measure_time(func): def wrapper(): start = time.time() func() end = time.time() print(f"Execution time: {end - start} seconds") return wrapper
Функция measure_time
возвращает другую вызванную функцию wrapper
, которая является модифицированной версией исходной функции и выполняет две задачи: записывает времена начала и окончания выполнения, вызывает исходную функцию.
Теперь можно использовать эту функцию так:
hello = measure_time(hello)
hello()
Получим:
Hello, world!
Execution time: 0.000123456789 seconds
Не изменяя код функции hello
мы успешно добавили в нее небольшую дополнительную функциональность. Но сделать это можно и более элегантным, лаконичным способом. Декораторы — это простой и удобный синтаксический прием, позволяющий применять одну функцию к другой, используя символ @
. Например, предыдущий код можно переписать так:
@measure_time def hello(): print("Hello, world!") hello()
Результат тот же, но код гораздо более компактный. Строка @measure_time
равноценна hello = measure_time(hello)
, но выглядит намного чище и проще.
Использование декораторов Python
Декораторы Python полезны во многих случаях. Вот несколько примеров.
- Повторное (без повторений) использование кода. Например, есть несколько функций, для которых нужно измерять время их выполнения. Чтобы повторно не переписывать весь код, можно просто применить ко всем функциям один и тот же декоратор.
- Разделение задачи и следование принципу единой ответственности. Например, есть функция сложного вычисления. Декораторы можно использовать для ведения журнала, обработки ошибок, кэширования или проверки входных и выходных данных, не загромождая основную логику функции.
- Расширение функциональности имеющихся функций или классов, не меняя исходный код. Например, вы используете стороннюю библиотеку с некоторыми полезными функциями или классами. Когда нужно расширить их функциональность, можно использовать декораторы, чтобы обернуть и настроить функции или классы в соответствии с возникшими требованиями.
Примеры декораторов Python
В Python есть много встроенных декораторов, включая @staticmethod
, @classmethod
, @property
, @functools.lru_cache
, @functools.singledispatch
и др. Можно создавать под различные задачи и собственные декораторы. Рассмотрим несколько таких примеров для сокращения объема кода.
1. Декоратор @timer
Этот декоратор похож на уже рассмотренный @measure_time
, но его можно применить к любой функции, принимающей любое количество аргументов и возвращающей любое значение. При этом он использует декоратор functools.wraps
для сохранения имени и строки документации (docstring) исходной функции. Вот код:
import from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"Execution time of {func.__name__}: {end - start} seconds") return result return wrapper
Используем этот декоратор для измерения времени выполнения любой функции, например:
@timer def factorial(n): """Returns the factorial of n""" if n == 0 or n == 1: return 1 else: return n * factorial(n - 1) @timer def fibonacci(n): """Returns the nth Fibonacci number""" if n == 0 or n == 1: return n else: return fibonacci(n - 1) + fibonacci(n - 2) print(factorial(10)) print(fibonacci(10))
Результат может быть таким:
Execution time of factorial: 1.1920928955078125e-06 seconds
3628800
Execution time of fibonacci: 0.000123456789 seconds
55
2. Декоратор @debug
Этот декоратор полезен при отладке, поскольку он выводит (prints) имя, аргументы и возвращаемое значение оборачиваемой функции. Он также использует декоратор functools.wraps
для сохранения имени и docstring исходной функции. Вот код:
from functools import wraps def debug(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with args: {args} and kwargs: {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} returned: {result}") return result return wrapper
Используем этот декоратор для отладки любой функции, например:
@debug def add(x, y): """Returns the sum of x and y""" return x + y @debug def greet(name, message="Hello"): """Returns a greeting message with the name""" return f"{message}, {name}!" print(add(2, 3)) print(greet("Alice")) print(greet("Bob", message="Hi"))
Результат может быть таким:
Calling add with args: (2, 3) and kwargs: {}
add returned: 5
5
Calling greet with args: ('Alice',) and kwargs: {}
greet returned: Hello, Alice!
Hello, Alice!
Calling greet with args: ('Bob',) and kwargs: {'message': 'Hi'}
greet returned: Hi, Bob!
Hi, Bob!
3. Декоратор @memoize
Этот декоратор полезен для оптимизации производительности рекурсивных или наиболее затратных функций, поскольку он кэширует результаты предыдущих вызовов и возвращает их, если те же аргументы передаются снова. Декоратор @memoize
также использует functools.wraps
для сохранения имени и docstring исходной функции. Вот код:
from functools import wraps def memoize(func): cache = {} @wraps(func) def wrapper(*args): if args in cache: return cache[args] else: result = func(*args) cache[args] = result return result return wrapper
Этот декоратор можно использовать для мемоизации (memoize) любой функции, например:
@memoize def factorial(n): """Returns the factorial of n""" if n == 0 or n == 1: return 1 else: return n * factorial(n - 1) @memoize def fibonacci(n): """Returns the nth Fibonacci number""" if n == 0 or n == 1: return n else: return fibonacci(n - 1) + fibonacci(n - 2) print(factorial(10)) print(fibonacci(10))
На выходе будет то же самое значение, но время выполнения значительно сократится за счет кэширования и повторного использования результатов.
Заключение
Декораторы Python — это мощный и элегантный способ изменения поведения функций или классов, не меняя исходный код. Декораторы способны: сократить код вдвое, улучшить его читаемость и повторное использование, разделить задачи и расширить функциональность имеющегося кода.