Применение декораторов в Python для изменения функций

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

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

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

Частые сценарии применения декораторов включают логирование вызовов, проверку прав доступа, измерение времени выполнения и кэширование результатов. Например, декоратор @lru_cache из стандартной библиотеки functools позволяет автоматически сохранять результаты функций, сокращая время вычислений при повторных вызовах с одинаковыми аргументами.

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

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

Как добавить логирование вызовов функции с помощью декоратора

Как добавить логирование вызовов функции с помощью декоратора

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

Пример декоратора:

import functools

def log_calls(func):

    @functools.wraps(func)

    def wrapper(*args, **kwargs):

        print(f'Вызов {func.__name__} с args={args}, kwargs={kwargs}')

        result = func(*args, **kwargs)

        print(f'{func.__name__} вернула {result}')

        return result

    return wrapper

Применение:

@log_calls

def multiply(a, b):

    return a * b

multiply(3, 5)

Для измерения времени выполнения добавьте import time и фиксируйте start = time.time() перед вызовом функции, а end = time.time() после. Разница end - start даст точное время работы функции.

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

Создание декоратора для измерения времени выполнения функции

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

Пример базового декоратора:

import time
def timer(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

Особенности реализации:

  • Используется time.perf_counter() для высокой точности измерений.
  • Аргументы функции передаются через *args и kwargs для сохранения универсальности декоратора.
  • Возвращается результат функции для корректного использования в дальнейшем коде.

Пример применения:

@timer
def calculate():
total = 0
for i in range(106):
total += i
return total
calculate()

Рекомендации для практического использования:

  1. Для многократного тестирования оборачивайте функции в цикл, чтобы получить среднее время выполнения.
  2. В больших проектах декоратор можно расширить логированием в файл вместо печати на экран.
  3. При измерении времени асинхронных функций используйте async def и await внутри декоратора.

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

Использование декораторов для проверки аргументов функции

Использование декораторов для проверки аргументов функции

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

def type_check(expected_type):

 def decorator(func):

  def wrapper(arg):

   if not isinstance(arg, expected_type):

    raise TypeError(f"Ожидается {expected_type}, получено {type(arg)}")

   return func(arg)

  return wrapper

 return decorator

Применение:

@type_check(int)
def factorial(n):
 if n == 0: return 1
 return n * factorial(n - 1)

Декоратор остановит выполнение при передаче некорректного типа, предотвращая скрытые ошибки.

Для проверки диапазона значений аргументов используется следующий подход:

def range_check(min_value, max_value):

 def decorator(func):

  def wrapper(arg):

   if not (min_value <= arg <= max_value):

    raise ValueError(f"Аргумент {arg} вне диапазона [{min_value}, {max_value}]")

   return func(arg)

  return wrapper

 return decorator

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

@range_check(1, 100)
def set_percentage(value):
 return f"Установлено {value}%"

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

Аргумент Тип Диапазон
age int 0–120
salary float 0–1_000_000
name str не пустая строка

Соответствующий декоратор может динамически проверять каждое правило:

def validate_args(rules):

 def decorator(func):

  def wrapper(kwargs):

   for arg, rule in rules.items():

    value = kwargs.get(arg)

    if not isinstance(value, rule['type']):

     raise TypeError(f"{arg}: ожидается {rule['type'].__name__}")

    if 'range' in rule and not (rule['range'][0] <= value <= rule['range'][1]):

     raise ValueError(f"{arg}: значение вне диапазона {rule['range']}")

   return func(kwargs)

  return wrapper

 return decorator

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

Применение декораторов для кэширования результатов функций

Применение декораторов для кэширования результатов функций

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

Пример применения @lru_cache для функции вычисления чисел Фибоначчи:

from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)

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

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

Пример собственного кэш-декоратора:

def cache_results(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_results
def compute_expensive(x, y):
return x  y + y  x

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

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

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

Модификация поведения функции с помощью вложенных декораторов

Модификация поведения функции с помощью вложенных декораторов

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

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

def uppercase(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
def add_exclamation(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + '!'
return wrapper
@add_exclamation
@uppercase
def greet(name):
return f"Hello, {name}"

В таблице ниже показано влияние порядка вложения декораторов:

Порядок декораторов Результат вызова greet("Alice")
@add_exclamation
@uppercase
HELLO, ALICE!
@uppercase
@add_exclamation
HELLO, ALICE!

При работе с вложенными декораторами рекомендуется:

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

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

Создание декоратора для обработки исключений внутри функции

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

Пример реализации базового декоратора:

def exception_handler(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Ошибка в функции {func.__name__}: {e}")
return None
return wrapper

Применение декоратора к функции происходит через синтаксис @exception_handler. Это автоматически оборачивает вызовы функции в блок try-except без изменения её внутренней логики.

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

def exception_handler(exceptions=(Exception,), logger=print):
def decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exceptions as e:
logger(f"Ошибка в {func.__name__}: {e}")
return None
return wrapper
return decorator

Рекомендуется сохранять оригинальное имя функции и документацию, используя functools.wraps:

from functools import wraps
def exception_handler(exceptions=(Exception,), logger=print):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exceptions as e:
logger(f"Ошибка в {func.name}: {e}")
return None
return wrapper
return decorator

Это гарантирует корректное поведение introspection и совместимость с инструментами документации и тестирования.

Передача параметров в декораторы для настройки функций

Передача параметров в декораторы для настройки функций

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

Пример передачи параметра в декоратор для логирования вызовов функции с разным уровнем детализации:

def logger(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == 'debug':
print(f"[DEBUG] Вызов функции {func.__name__} с аргументами {args} {kwargs}")
result = func(*args, **kwargs)
if level == 'debug':
print(f"[DEBUG] Результат: {result}")
return result
return wrapper
return decorator
@logger(level='debug')
def multiply(a, b):
return a * b
multiply(3, 4)

Рекомендации по работе с параметризованными декораторами:

  • Используйте functools.wraps для сохранения имени и документации исходной функции:
  • from functools import wraps
    def decorator_with_wraps(param):
    def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
    # логика декоратора
    return func(*args, **kwargs)
    return wrapper
    return decorator
    
  • Для нескольких параметров декоратора применяйте именованные аргументы, чтобы избежать ошибок при вызове.
  • Минимизируйте сложность внутреннего wrapper: вынесите вспомогательные вычисления во внешний уровень или отдельные функции.
  • Используйте параметры для выбора режимов работы: логирование, кеширование, проверка типов, ограничение времени выполнения.
  • Проверяйте типы и значения параметров декоратора сразу в внешней функции, чтобы обнаружить ошибки до применения к функции.

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

Применение декораторов для контроля доступа к функциям

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

Простейший пример – декоратор, проверяющий роль пользователя:

Пример:

def require_role(role):
def decorator(func):
def wrapper(user, *args, **kwargs):
if role not in user.roles:
raise PermissionError(f'Доступ запрещен для роли {role}')
return func(user, *args, **kwargs)
return wrapper
return decorator

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

@require_role('admin')
def delete_user(user, user_id):
# удаление пользователя
pass

Здесь user – объект с атрибутом roles. Декоратор проверяет наличие роли и предотвращает выполнение функции при недостаточных правах.

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

Можно создавать универсальные декораторы, принимающие несколько допустимых ролей:

def require_roles(*roles):
def decorator(func):
def wrapper(user, *args, **kwargs):
if not any(r in user.roles for r in roles):
raise PermissionError(f'Доступ разрешен только для ролей: {roles}')
return func(user, *args, **kwargs)
return wrapper
return decorator

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

Декораторы для контроля доступа также могут работать с токенами и сессиями, проверяя их актуальность и срок действия, что критично для REST API и микросервисной архитектуры.

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

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

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

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

Как создать декоратор, который принимает аргументы?

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

Можно ли применять несколько декораторов к одной функции?

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

Влияют ли декораторы на производительность программы?

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

Как использовать стандартные декораторы Python, например @staticmethod и @classmethod?

Декораторы @staticmethod и @classmethod применяются к методам классов для изменения их поведения. @staticmethod делает метод независимым от экземпляра класса, он не получает self. @classmethod передаёт классу сам класс через параметр cls, что позволяет создавать альтернативные конструкторы и управлять состоянием класса. Их использование упрощает структуру кода и делает методы более универсальными.

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