Лучший опыт

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