Для чего используются декораторы в python

Для чего используются декораторы в python

Декораторы в Python позволяют оборачивать функции и методы дополнительной логикой без изменения их исходного кода. Это инструмент, активно применяемый в логировании, проверке прав доступа, кэшировании, повторной попытке выполнения, измерении времени и модификации аргументов вызова. Их использование повышает читаемость и повторное использование кода, особенно в больших проектах.

Простой способ создания декоратора – определить функцию, принимающую другую функцию как аргумент и возвращающую новую. Пример:

@log_time

def fetch_data():

    pass

Здесь log_time – это декоратор, который можно переиспользовать для отслеживания времени выполнения любых функций. Реализация:

def log_time(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

Для передачи аргументов декоратору используется вложенность. Например, ограничение числа вызовов функции за период времени:

@rate_limit(calls=5, period=60)

В этом случае внешний декоратор получает параметры и возвращает стандартный декоратор. Это активно используется при интеграции с API, чтобы избежать превышения квоты запросов.

Декораторы применяются также в классах. С помощью @classmethod, @staticmethod и @property реализуются разные модели доступа и поведения. Пример:

class Config:
    @property
    def db_uri(self):
        return f"{self.driver}://{self.host}"

Декораторы можно комбинировать, располагая их стеком над функцией. Порядок имеет значение: выполнение начинается с самого верхнего. Это позволяет составлять сложное поведение из простых компонентов, избегая дублирования.

Для сохранения сигнатуры обёрнутой функции рекомендуется использовать functools.wraps. Без этого имя и документация исходной функции будут утеряны, что мешает отладке и автогенерации документации.

Логирование вызовов функций с параметрами

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

Пример простого декоратора с логированием параметров:

import logging
from functools import wraps
logging.basicConfig(level=logging.INFO)
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(
"Вызов %s с позиционными аргументами %s и именованными аргументами %s",
func.__name__, args, kwargs
)
return func(*args, **kwargs)
return wrapper

Применение:

@log_call
def calculate(a, b, operation="add"):
if operation == "add":
return a + b
elif operation == "sub":
return a - b

При вызове calculate(3, 5, operation="sub") в журнале появится запись:

INFO:root:Вызов calculate с позиционными аргументами (3, 5) и именованными аргументами {'operation': 'sub'}

Если необходимо логировать также результат выполнения, декоратор можно дополнить строкой result = func(*args, kwargs) и отдельной записью logging.info("Результат: %s", result).

Чтобы исключить из логирования чувствительные данные, можно добавить фильтрацию по ключам в kwargs или использовать регулярные выражения для маскировки значений. Например:

safe_kwargs = {k: ("*" if "password" in k else v) for k, v in kwargs.items()}

Такой подход снижает риск утечки информации при использовании логов в тестировании или в продуктивной среде.

Ограничение частоты выполнения с помощью декоратора

Функции, вызываемые слишком часто, могут перегружать систему или внешние API. Для ограничения частоты вызова удобно использовать декоратор с таймером. Это позволяет задать минимальный интервал между вызовами и блокировать лишние обращения.

Пример простого декоратора, ограничивающего вызов функции раз в n секунд:

import time
from functools import wraps
def rate_limit(interval):
def decorator(func):
last_call = [0]
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
if now - last_call[0] >= interval:
last_call[0] = now
return func(*args, **kwargs)
return wrapper
return decorator

Если функция вызывается чаще, чем разрешено, она просто не выполняется. При необходимости можно заменить поведение, например, выбрасывать исключение:

def rate_limit(interval):
def decorator(func):
last_call = [0]
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
if now - last_call[0] < interval:
raise RuntimeError("Слишком частый вызов")
last_call[0] = now
return func(*args, **kwargs)
return wrapper
return decorator

Если декорируемая функция работает с многопоточностью, использовать списки как замыкания недостаточно. Следует использовать threading.Lock:

import threading
def thread_safe_rate_limit(interval):
def decorator(func):
last_call = [0]
lock = threading.Lock()
@wraps(func)
def wrapper(*args, **kwargs):
with lock:
now = time.time()
if now - last_call[0] < interval:
raise RuntimeError("Превышен лимит частоты")
last_call[0] = now
return func(*args, **kwargs)
return wrapper
return decorator

Такой подход подходит для защиты API, предотвращения повторной отправки форм и ограничения вызовов фоновых задач.

Кеширование результатов вычислений вручную и с functools.lru_cache

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

Ручная реализация кеша:

cache = {}
def slow_function(x):
if x in cache:
return cache[x]
result = x * x  # Заменить на тяжёлую операцию
cache[x] = result
return result
  • Подходит для простых случаев.
  • Полный контроль над структурой хранения.
  • Не подходит для многопоточного окружения без синхронизации.

Использование functools.lru_cache:

from functools import lru_cache
@lru_cache(maxsize=128)
def slow_function(x):
return x * x
  • maxsize определяет максимальное количество сохранённых результатов. При переполнении удаляются самые старые.
  • Работает только с хэшируемыми аргументами.
  • Кеш автоматически очищается при выходе из программы.

Для сброса кеша используется метод:

slow_function.cache_clear()

Проверка текущего состояния кеша:

slow_function.cache_info()
  • Возвращает namedtuple с полями: hits, misses, maxsize, currsize.
  • Позволяет оценить, насколько кеш помогает сократить количество вызовов.

Если требуется потокобезопасность – использовать threading.Lock при ручной реализации или доверять lru_cache, который безопасен для большинства сценариев.

Авторизация и контроль доступа через декораторы

Авторизация и контроль доступа через декораторы

Декораторы в Python позволяют внедрять проверку прав доступа к функциям без изменения их кода. Это особенно полезно в веб-приложениях, где необходимо ограничивать доступ к представлениям или API в зависимости от ролей пользователей.

Типичный пример – проверка, обладает ли пользователь правами администратора. Ниже представлен базовый декоратор, выполняющий такую проверку:

from functools import wraps
from flask import request, abort
def require_admin(f):
@wraps(f)
def decorated_function(*args, **kwargs):
user = getattr(request, 'user', None)
if not user or user.role != 'admin':
abort(403)
return f(*args, **kwargs)
return decorated_function

Применение:

@require_admin
def delete_user(user_id):
# Удаление пользователя из базы данных
pass

Если пользователь не авторизован или не является администратором, функция не будет выполнена. Этот подход изолирует логику контроля доступа и упрощает сопровождение кода.

Для гибкости можно использовать декоратор с параметром:

def require_role(*roles):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
user = getattr(request, 'user', None)
if not user or user.role not in roles:
abort(403)
return f(*args, **kwargs)
return wrapper
return decorator

Пример использования:

@require_role('editor', 'admin')
def edit_article(article_id):
pass

Это избавляет от необходимости создавать отдельные декораторы для каждой роли. В реальных приложениях можно также учитывать уровень доступа, флаги блокировки, состояния аккаунта и другие параметры авторизации.

Измерение времени выполнения функций

Измерение времени выполнения функций

Декоратор позволяет обернуть любую функцию для автоматического замера времени её выполнения без изменения исходного кода. Для этого можно использовать модуль time или timeit.

Простейший вариант основан на функции time.perf_counter(), которая обеспечивает высокоточное измерение интервалов:

import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"Время выполнения {func.__name__}: {end - start:.6f} секунд")
return result
return wrapper

Этот подход подходит для любых функций, включая те, что работают с I/O или сетью. Для многократного замера и усреднения времени лучше применять timeit:

import timeit
def timing_decorator(func):
def wrapper(*args, **kwargs):
stmt = lambda: func(*args, **kwargs)
duration = timeit.timeit(stmt, number=1)
print(f"Время выполнения {func.__name__}: {duration:.6f} секунд")
return func(*args, **kwargs)
return wrapper

Во втором варианте функция вызывается дважды, что неприемлемо для функций с побочными эффектами. В таких случаях нужно использовать только perf_counter().

Для многократного запуска и анализа распределения времени полезно добавить параметр repeat:

def benchmark(repeat=5):
def decorator(func):
def wrapper(*args, **kwargs):
times = []
for _ in range(repeat):
start = time.perf_counter()
func(*args, **kwargs)
end = time.perf_counter()
times.append(end - start)
avg = sum(times) / repeat
print(f"Среднее время выполнения {func.__name__}: {avg:.6f} секунд")
return wrapper
return decorator

Такой подход позволяет получить более стабильные значения за счёт усреднения. Использовать замер времени через декораторы удобно при оптимизации производительности и сравнении реализаций.

Переопределение поведения метода без изменения класса

В Python переопределение методов можно осуществлять не только изменяя исходный класс, но и динамически, с помощью декораторов. Это позволяет менять поведение методов без необходимости изменять сам класс или его исходный код. Один из популярных способов такого подхода – использование функций, которые оборачивают оригинальный метод и добавляют или изменяют его логику.

Для начала, рассмотрим пример, когда мы хотим изменить поведение метода в определённом классе без прямого изменения его кода. Создадим декоратор, который будет изменять возвращаемое значение метода.

pythonEditdef change_return_value(func):

def wrapper(*args, **kwargs):

result = func(*args, **kwargs)

return result * 2 # Удваиваем результат работы метода

return wrapper

Теперь применим этот декоратор к методу класса. Вместо того чтобы изменять сам метод, мы создаём декоратор и применяем его. Это позволяет нам не трогать оригинальный код класса.

pythonCopyEditclass MyClass:

def get_value(self):

return 5

@change_return_value

def get_doubled_value(self):

return 5

Когда мы вызовем метод get_doubled_value, результат будет умножен на 2, что демонстрирует переопределение поведения метода:

pythonCopyEditobj = MyClass()

print(obj.get_doubled_value()) # Выведет 10

Декоратор оборачивает метод, и таким образом, мы не изменяем сам класс, а лишь модифицируем логику его методов. Это даёт возможность централизованно управлять поведением методов, что полезно при работе с большими проектами, где изменения в исходных классах могут быть нежелательны.

При работе с декораторами важно помнить, что они могут добавлять не только функциональность, но и производить дополнительные проверки или модификации. Например, можно создать декоратор, который будет проверять параметры метода:

pythonCopyEditdef validate_args(func):

def wrapper(*args, **kwargs):

if not all(isinstance(arg, int) for arg in args):

raise ValueError("Все аргументы должны быть целыми числами")

return func(*args, **kwargs)

return wrapper

Этот декоратор можно применить к методу, чтобы удостовериться, что все переданные аргументы соответствуют типу int. Такая проверка может быть полезной в ситуациях, когда требуется гарантировать правильность данных.

pythonCopyEditclass MyClass:

@validate_args

def add_numbers(self, a, b):

return a + b

Вызов метода с некорректными типами аргументов вызовет исключение:

pythonCopyEditobj = MyClass()

obj.add_numbers(5, "hello") # Поднимет ValueError

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

Регистрация функций в реестре обработчиков

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

Пример реестра обработчиков

Для начала создадим структуру данных, которая будет хранить все зарегистрированные обработчики. Рассмотрим использование словаря, где ключи – это события, а значения – списки обработчиков.


handlers_registry = {}
def register_handler(event_name):
def decorator(func):
if event_name not in handlers_registry:
handlers_registry[event_name] = []
handlers_registry[event_name].append(func)
return func
return decorator

В этом примере декоратор register_handler регистрирует функции в реестре обработчиков для каждого события. Для каждого события (например, "event_a") будет сохраняться список обработчиков, которые вызовутся при наступлении этого события.

Пример использования

Теперь можно использовать наш декоратор для регистрации различных обработчиков для разных событий:


@register_handler("event_a")
def handler_a():
print("Обработчик события A")
@register_handler("event_b")
def handler_b():
print("Обработчик события B")

После регистрации обработчиков для событий можно создать функцию, которая будет вызывать все зарегистрированные обработчики для конкретного события:


def trigger_event(event_name):
if event_name in handlers_registry:
for handler in handlers_registry[event_name]:
handler()
else:
print(f"Нет обработчиков для события: {event_name}")

Динамическое добавление обработчиков

Регистрация обработчиков может быть динамической, что особенно полезно для разработки плагинов или при необходимости подключать новые обработчики во время выполнения программы. Например, можно добавить новый обработчик без изменения основного кода:


def handler_c():
print("Обработчик события C")
register_handler("event_a")(handler_c)

В этом примере мы добавили новый обработчик handler_c для события "event_a" после его первоначальной регистрации.

Удаление обработчиков

Удаление обработчиков

В некоторых случаях может возникнуть необходимость удалить обработчики. Для этого можно добавить дополнительную логику для управления реестром, например, метод для удаления обработчика:


def remove_handler(event_name, handler):
if event_name in handlers_registry:
handlers_registry[event_name].remove(handler)

После удаления обработчик больше не будет вызываться при наступлении события.

Преимущества

  • Упрощение управления обработчиками событий.
  • Гибкость добавления и удаления обработчиков без необходимости менять основной код.
  • Повышение читаемости кода за счёт использования декораторов.

Заключение

Регистрация функций в реестре обработчиков с использованием декораторов позволяет создавать удобные и масштабируемые системы для обработки событий. Этот подход не только улучшает структуру кода, но и делает приложение более гибким и расширяемым. Использование таких методов в реальной практике облегчает добавление новых функциональностей и упрощает работу с событиями в сложных приложениях.

Передача аргументов в декоратор с использованием вложенных функций

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

Стандартная структура декоратора включает в себя внешний и внутренний уровни функций. Внешняя функция принимает аргументы, которые должны быть переданы в декоратор, а внутренняя функция отвечает за изменение поведения декорируемой функции. Такой подход позволяет динамически изменять поведение декоратора, а также гибко управлять его параметрами.

Пример простого декоратора, принимающего параметры, выглядит следующим образом:

def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator

В этом примере внешний декоратор repeat принимает аргумент times, который определяет количество повторений для декорируемой функции. Внутренняя функция wrapper выполняет функцию func нужное количество раз.

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

@repeat(3)
def greet():
print("Hello!")
greet()

Этот код выведет строку "Hello!" три раза. Передавая параметры в декоратор, можно настроить его под любые нужды.

Важно помнить, что такие декораторы с параметрами должны быть реализованы с использованием вложенных функций, чтобы правильно обрабатывать переданные аргументы. Это также позволяет избежать проблем с передачей состояний между разными вызовами функций.

Еще одним примером использования вложенных функций может быть декоратор для логирования:

def log(level):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{level}] Calling function {func.__name__} with arguments {args} and {kwargs}")
return func(*args, **kwargs)
return wrapper
return decorator

Здесь log – это внешний декоратор, принимающий аргумент level, который указывает уровень логирования. Внутренняя функция wrapper добавляет логи перед вызовом функции.

Для применения этого декоратора нужно передать уровень логирования, например:

@log("INFO")
def add(a, b):
return a + b
add(3, 4)

В результате выполнения программы будет выведено сообщение с уровнем логирования и аргументами функции. Это позволяет создавать более настраиваемые и мощные декораторы.

Использование вложенных функций для передачи аргументов в декораторы – это мощный инструмент, который открывает широкие возможности для гибкой настройки поведения функций в Python. Такой подход легко расширяется и может быть адаптирован под различные задачи, от простого повторения до сложных операций логирования или мониторинга.

Вопрос-ответ:

Что такое декораторы в Python и как они работают?

Декораторы в Python — это функции, которые принимают другую функцию в качестве аргумента и возвращают новую функцию, расширяя или изменяя её поведение. Они часто используются для добавления функциональности к существующим функциям, без изменения их кода. Например, декоратор может использоваться для логирования вызовов функций, измерения времени выполнения или проверки прав доступа.

Как создать свой декоратор в Python?

Для создания декоратора в Python необходимо написать функцию, которая принимает функцию в качестве аргумента и возвращает новую функцию. Новая функция может вызвать исходную и добавить дополнительные действия. Пример простого декоратора:

Как использовать аргументы в декораторах?

Если функция, к которой применяется декоратор, принимает аргументы, то декоратор также должен поддерживать их. Для этого внутри декоратора можно использовать `*args` и `**kwargs`, чтобы передавать все аргументы в исходную функцию. Вот пример:

Когда стоит использовать декораторы, а когда лучше отказаться от их применения?

Декораторы полезны, когда нужно добавить одинаковую функциональность нескольким функциям, например, для логирования, проверки прав доступа, или кэширования. Они позволяют избежать дублирования кода. Однако, если поведение слишком сложное или влияет на логику работы функции, лучше избегать использования декораторов, чтобы не усложнять код и не снижать его читаемость.

Как применять декораторы с аргументами в классе?

Для применения декораторов в классах можно использовать методы экземпляра или статические методы. В случае, если декоратор должен работать с методами, следует учитывать, что первым аргументом будет экземпляр класса (`self`). Пример использования декоратора для методов класса:

Как декораторы могут улучшить читаемость и структуру кода в Python?

Декораторы позволяют инкапсулировать логику, которая повторяется в нескольких местах, в одном месте, тем самым улучшая структуру программы. Это позволяет избежать дублирования кода и делает его более чистым и понятным. Например, декоратор для логирования может быть применён ко всем функциям, которые должны записывать информацию о своих вызовах, без необходимости добавлять код логирования в каждой функции вручную. Так мы добиваемся удобства и поддерживаемости кода.

Ссылка на основную публикацию