Декораторы в Python

Используйте декораторы для повышения повторного использования кода и организации вашего Python-проекта. Например, представьте, что у вас есть несколько функций, принимающих на вход JSON-строку и возвращающих данные в удобном формате. Вместо написания одинаковой обработки ошибок и преобразований в каждой функции, вы можете сконструировать декоратор.
Этот подход значительно упрощает код, делая его более читаемым и поддерживаемым при росте проекта. Ускорит разработку, сводит к минимуму повторения и потенциальные ошибки.
Рассмотрим пример: предположим, у вас есть функция для работы с JSON, которая может вызывать исключение JSONDecodeError
. Вместо того, чтобы обрабатывать это исключение в каждой функции, используйте декоратор. Декоратор позволит обрабатывать ошибку единообразно для всех функций, к которым он применён. Это повысит надёжность и качество кода.
Что такое декоратор и зачем он нужен?
Зачем это нужно? Для изменения поведения исходной функции без необходимости её прямого изменения.
- Пример 1: Добавление логирования.
Представьте функцию, которая что-то вычисляет:
python
def вычисление(x, y):
z = x + y
print(f"Вычисляем: {x} + {y} = {z}")
return z
Но вам нужно следить за всеми вызовами. Декоратор решает эту задачу:
python
import functools
def логгирование(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
print(f"Вызов функции {функция.__name__} с аргументами {args}, {kwargs}")
значение = функция(*args, **kwargs)
print(f"Результат: {значение}")
return значение
return обертка
@логгирование
def вычисление(x, y):
z = x + y
return z
вычисление(5, 3)
Декоратор логгирование
добавляет логирование в функцию вычисление
, не трогая её код!
- Пример 2: Добавление проверки параметров.
- Пример 3: Изменение поведения функций с помощью дополнительных аргументов.
- Пример 4: Добавление кеширования результатов вычислений.
- Пример 5: Автоматическое закрытие ресурсов (файлов, соединений).
С помощью декораторов вы можете добавлять новые возможности к функциям чисто как вводные переменные или поведение в вызове функции без изменения самой функции.
Как создать простой декоратор в Python?
Для создания простого декоратора в Python используйте синтаксис, основанный на функции-обёртке:
def мой_декоратор(функция):
def обёртка():
print("Вызов функции до выполнения")
функция()
print("Вызов функции после выполнения")
return обёртка
Затем, для применения декоратора к функции, используйте синтаксис:
@мой_декоратор
def моя_функция():
print("Привет, мир!")
Звонок моя_функция()
теперь выведет:
Вызов функции до выполнения
Привет, мир!
Вызов функции после выполнения
Ключевой момент: мой_декоратор
принимает функцию в качестве аргумента и возвращает изменённую обёртку. Обёртка выполняет дополнительные действия до и после вызова исходной функции.
Более сложные декораторы могут принимать аргументы, возвращать значения и содержать большую логику, но базовая структура остаётся такой же.
Работа с аргументами и возвращаемыми значениями в декораторах.
Ключевой момент в работе с декораторами – правильное управление аргументами и возвращаемыми значениями. Декораторы должны адаптироваться к функциям, которые они декорируют, без потери функциональности.
Задача | Решение | Пример |
---|---|---|
Передача аргументов декорируемой функции | Используйте *args и **kwargs внутри декоратора. |
def my_decorator(func): def wrapper(*args, **kwargs): print("Аргументы:", args, kwargs) result = func(*args, **kwargs) print("Результат:", result) return result return wrapper @my_decorator def greet(name, age): return f"Привет, {name}! Тебе {age} лет." greet("Вася", 30) |
Изменение возвращаемого значения | Возвращайте результат из внутренней функции wrapper. |
def my_decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result * 2 # Изменяем возврат return wrapper @my_decorator def square(x): return x * x |
Обработка исключений | Используйте try-except внутри декоратора. |
import time def time_it(func): def wrapper(*args, **kwargs): try: start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Время выполнения: {end_time - start_time:.4f} секунд") return result except Exception as e: print(f"Ошибка: {e}") return None # Обрабатываем ошибку @time_it def slow_function(n): result = 0 for i in range(n): result += i return result slow_function(1000000) |
Поняв эти принципы, вы сможете создавать гибкие и мощные декораторы, которые адаптируются к различным потребностям.
Использование декораторов с классами и методами.
Декораторы применяются к методам класса, изменяя их поведение без непосредственного изменения кода метода. Вот практический пример:
Представьте класс Calculator
. Хотите добавить логирование для всех методов:
import logging def log_method_calls(func): def wrapper(*args, **kwargs): logging.info(f"Вызов метода {func.__name__} с аргументами: {args}, {kwargs}") result = func(*args, **kwargs) logging.info(f"Результат метода {func.__name__}: {result}") return result return wrapper class Calculator: @log_method_calls def add(self, a, b): return a + b @log_method_calls def subtract(self, a, b): return a - b #Настройка логирования logging.basicConfig(level=logging.INFO) calc = Calculator() result = calc.add(5, 3) result = calc.subtract(10, 4)
Декоратор log_method_calls
логгирует вызовы методов и результаты. Обратите внимание на использование @log_method_calls
над методами add
и subtract
. Это ключевое место для применения.
Для классов, декоратор применяется аналогично. Он позволяет выполнять действия до и после вызова методов класса или настраивать доступ к атрибутам.
Практические примеры применения декораторов.
Для более эффективного логирования вызовов функций используйте декоратор:
import datetime def log_function_call(func): def wrapper(*args, **kwargs): timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{timestamp} Вызов функции {func.__name__} с аргументами: {args}, {kwargs}") result = func(*args, **kwargs) print(f"{timestamp} Результат: {result}") return result return wrapper @log_function_call def my_function(a, b): return a + b my_function(5, 3)
Декоратор для ограничения доступа к функции:
import functools def restricted_access(func): @functools.wraps(func) def wrapper(*args, **kwargs): if 'admin' in args[0] and args[0].get('admin'): return func(*args, **kwargs) else: print("Доступ запрещен.") return None return wrapper @restricted_access def sensitive_function(data, admin=False): print("Доступ получен. Данные:"); print(data) return data sensitive_function({'a': 1}, admin = True)
Декоратор, кеширующий результаты функции:
import functools def memoize(func): cache = {} @functools.wraps(func) def wrapper(*args): if args in cache: return cache[args] result = func(*args) cache[args] = result return result return wrapper @memoize def expensive_calculation(x): print(f"Вычисление для {x}...") return x * x print(expensive_calculation(5)) print(expensive_calculation(5))
- Первый пример показывает логирование вызовов функций с точным временем.
- Второй пример демонстрирует ограничение доступа по параметру
admin
. - Третий пример иллюстрирует кеширование результатов для оптимизации, избегая повторяющихся вычислений.
Используйте декораторы для повышения читаемости, повторного использования кода и безопасного управления функциями.
Когда стоит и не стоит использовать декораторы.
Стоит использовать декораторы, когда нужно обернуть функцию дополнительной логикой, не изменяя её исходный код. Например, для логгирования, измерения времени выполнения или аутентификации.
Пример: Декоратор для добавления логирования перед вызовом функции:
import time def log_execution_time(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Функция {func.__name__} выполнилась за {end_time - start_time:.4f} секунд") return result return wrapper @log_execution_time def my_function(n): time.sleep(n) return 'Результат!'
Здесь декоратор log_execution_time
добавляет измерение времени без изменения my_function
.
Не стоит использовать декораторы, когда это усложняет код без явной пользы. Особенно это касается простых задач. Не нужно придумывать декораторы для каждой вспомогательной функции. Напрямую вызывать функцию может быть проще и понятнее.
Пример: Использование декоратора для простых проверок:
def check_age(func): def wrapper(age): if age < 18: return "Несовершеннолетний" return func(age) return wrapper @check_age def my_function(age): return "Вам " + str(age) + " лет"
В данном случае, проверку возраста можно выполнить напрямую в функции, без декоратора.
Ключевой фактор: Декораторы полезны для рекурсивных или частых операций. Но они не являются панацеей для всех случаев.
Вопрос-ответ:
Как декоратор передает информацию функции?
Декоратор передает информацию функции через оберточную функцию. Внутри обёртки вы можете получить доступ к информации о функциях или ее атрибутам, а также к аргументам и результату выполнения функции и использовать её. Сама функция-декоратор получает исходную функцию как аргумент (передается как аргумент в inner-функцию обёртки).
Когда использовать декораторы?
Декораторы полезны, когда нужно внести некоторую функциональность в большое число функций без значительного изменения кода каждой из них. Например, это может быть логирование, кеширование, авторитезация или валидация. Если вам нужно добавить общую задачу к нескольким функциям, то декораторы будут полезны. Можно с их помощью создавать гибкие и переиспользуемые инструменты для вашего кода.
Курсы


.png)

.png)

.png)
