Лучший опыт

Эти декораторы Python позволят сократить код вдвое.

Эти декораторы 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  —  это мощный и элегантный способ изменения поведения функций или классов, не меняя исходный код. Декораторы способны: сократить код вдвое, улучшить его читаемость и повторное использование, разделить задачи и расширить функциональность имеющегося кода.