Декораторы в Python - это мощный инструмент, который позволяет изменять поведение функций или методов без изменения их исходного кода. Декоратор - это функция, которая принимает другую функцию в качестве аргумента и возвращает новую функцию, обычно расширяя или изменяя поведение исходной функции. Декораторы используются для добавления функциональности, такой как логирование, измерение времени выполнения, проверка прав доступа и многое другое.
Декораторы в Python используют синтаксис @имя_декоратора перед определением функции:
# Простой декоратор, который выводит сообщение до и после выполнения функции
def декоратор_простой(функция):
def обертка(*args, **kwargs):
print("Вызов функции:", функция.__name__)
результат = функция(*args, **kwargs)
print("Функция завершена:", функция.__name__)
return результат
return обертка
# Применение декоратора
@декоратор_простой
def приветствие(имя):
print(f"Привет, {имя}!")
@декоратор_простой
def сложение(a, b):
return a + b
# Вызов декорированных функций
приветствие("Анна")
результат = сложение(5, 3)
print(f"Результат сложения: {результат}")
# Эквивалент без использования синтаксиса @
def умножение(a, b):
return a * b
# Ручное применение декоратора
умножение = декоратор_простой(умножение)
результат2 = умножение(4, 7)
print(f"Результат умножения: {результат2}")
Декораторы могут принимать параметры, что делает их более гибкими и мощными:
# Декоратор с параметрами для повторения выполнения функции
def повторить(количество_раз):
def декоратор_повтора(функция):
def обертка(*args, **kwargs):
результаты = []
for i in range(количество_раз):
print(f"Вызов #{i+1}:")
результат = функция(*args, **kwargs)
результаты.append(результат)
return результаты
return обертка
return декоратор_повтора
# Применение декоратора с параметрами
@повторить(3)
def бросить_кубик():
import random
return random.randint(1, 6)
# Вызов функции, которая будет выполнена 3 раза
результаты = бросить_кубик()
print(f"Результаты бросков: {результаты}")
# Декоратор для ограничения количества вызовов функции
def ограничить_вызовы(максимум_вызовов):
def декоратор_ограничения(функция):
функция.вызовы = 0
def обертка(*args, **kwargs):
if функция.вызовы >= максимум_вызовов:
raise RuntimeError(f"Функция {функция.__name__} может быть вызвана максимум {максимум_вызовов} раз")
функция.вызовы += 1
return функция(*args, **kwargs)
return обертка
return декоратор_ограничения
# Применение декоратора ограничения вызовов
@ограничить_вызовы(2)
def защищенная_функция():
return "Доступ разрешен"
# Попытка вызвать функцию несколько раз
try:
print(защищенная_функция())
print(защищенная_функция())
print(защищенная_функция()) # Это вызовет ошибку
except RuntimeError as e:
print(f"Ошибка: {e}")
Декораторы могут быть реализованы не только как функции, но и как классы:
# Классовый декоратор для подсчета вызовов функции
class СчетчикВызовов:
def __init__(self, функция):
self.функция = функция
self.количество_вызовов = 0
def __call__(self, *args, **kwargs):
self.количество_вызовов += 1
print(f"Функция {self.функция.__name__} вызвана {self.количество_вызовов} раз")
return self.функция(*args, **kwargs)
# Применение классового декоратора
@СчетчикВызовов
def приветствовать(имя):
return f"Привет, {имя}!"
# Вызов функции несколько раз
print(приветствовать("Иван"))
print(приветствовать("Мария"))
print(приветствовать("Алексей"))
# Классовый декоратор с параметрами
class КэшированиеРезультатов:
def __init__(self, функция):
self.функция = функция
self.кэш = {}
def __call__(self, *args):
if args in self.кэш:
print(f"Возвращаем кэшированный результат для {args}")
return self.кэш[args]
else:
print(f"Вычисляем результат для {args}")
результат = self.функция(*args)
self.кэш[args] = результат
return результат
# Применение классового декоратора для кэширования
@КэшированиеРезультатов
def факториал(n):
if n <= 1:
return 1
return n * факториал(n - 1)
# Вызов функции факториала
print(факториал(5))
print(факториал(3)) # Частично кэширован
print(факториал(5)) # Полностью кэширован
Декораторы находят широкое применение в реальных проектах для различных задач:
import time
import functools
# Декоратор для логирования вызовов функций
def логировать(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
print(f"Вызов функции {функция.__name__} с аргументами {args} и {kwargs}")
начало = time.time()
результат = функция(*args, **kwargs)
конец = time.time()
print(f"Функция {функция.__name__} завершена за {конец - начало:.4f} секунд")
return результат
return обертка
# Декоратор для измерения времени выполнения
def измерить_время(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
начало = time.perf_counter()
результат = функция(*args, **kwargs)
конец = time.perf_counter()
print(f"{функция.__name__} заняла {конец - начало:.6f} секунд")
return результат
return обертка
# Применение декораторов
@логировать
@измерить_время
def вычислить_сумму(n):
return sum(range(n))
# Вызов функции с несколькими декораторами
результат = вычислить_сумму(1000000)
print(f"Сумма: {результат}")
# Декоратор для проверки типов аргументов
def проверить_типы(**типы_аргументов):
def декоратор_проверки(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
# Получаем имена параметров функции
сигнатура = functools.signature(функция)
привязанные_аргументы = сигнатура.bind(*args, **kwargs)
привязанные_аргументы.apply_defaults()
# Проверяем типы
for имя_аргумента, значение in привязанные_аргументы.arguments.items():
if имя_аргумента in типы_аргументов:
ожидаемый_тип = типы_аргументов[имя_аргумента]
if not isinstance(значение, ожидаемый_тип):
raise TypeError(f"Аргумент '{имя_аргумента}' должен быть типа {ожидаемый_тип.__name__}")
return функция(*args, **kwargs)
return обертка
return декоратор_проверки
# Применение декоратора проверки типов
@проверить_типы(a=int, b=int)
def умножить(a, b):
return a * b
# Корректный вызов
print(умножить(5, 3))
# Некорректный вызов (вызовет ошибку)
try:
умножить("5", 3)
except TypeError as e:
print(f"Ошибка: {e}")
# Декоратор для повторных попыток выполнения функции при ошибках
def повторить_при_ошибке(максимум_попыток=3, задержка=1):
def декоратор_повтора(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
for попытка in range(максимум_попыток):
try:
return функция(*args, **kwargs)
except Exception as e:
if попытка == максимум_попыток - 1:
raise e
print(f"Попытка {попытка + 1} не удалась: {e}. Повтор через {задержка} секунд...")
time.sleep(задержка)
return обертка
return декоратор_повтора
# Декоратор для игнорирования исключений
def игнорировать_исключения(*типы_исключений, значение_по_умолчанию=None):
def декоратор_игнорирования(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
try:
return функция(*args, **kwargs)
except типы_исключений as e:
print(f"Исключение проигнорировано: {e}")
return значение_по_умолчанию
return обертка
return декоратор_игнорирования
# Пример функции, которая может вызывать ошибки
import random
@повторить_при_ошибке(максимум_попыток=5, задержка=0.5)
def ненадежная_функция():
if random.random() < 0.7: # 70% шанс ошибки
raise ConnectionError("Ошибка подключения")
return "Успешное выполнение"
# Вызов ненадежной функции
try:
результат = ненадежная_функция()
print(f"Результат: {результат}")
except ConnectionError as e:
print(f"Все попытки неудачны: {e}")
@игнорировать_исключения(ZeroDivisionError, ValueError, значение_по_умолчанию=0)
def деление_с_защитой(a, b):
return a / b
# Вызов функции с обработкой исключений
print(деление_с_защитой(10, 2)) # Нормальное выполнение
print(деление_с_защитой(10, 0)) # ZeroDivisionError будет проигнорирован
print(деление_с_защитой("10", 2)) # ValueError будет проигнорирован
Рассмотрим более сложные примеры использования декораторов:
# Декоратор для проверки прав доступа
def требует_прав(права):
def декоратор_прав(метод):
@functools.wraps(метод)
def обертка(self, *args, **kwargs):
if not hasattr(self, 'права') or права not in self.права:
raise PermissionError(f"Требуются права: {права}")
return метод(self, *args, **kwargs)
return обертка
return декоратор_прав
# Декоратор для кэширования результатов методов
def кэшировать_метод(метод):
кэш = {}
@functools.wraps(метод)
def обертка(self, *args, **kwargs):
ключ_кэша = str(args) + str(sorted(kwargs.items()))
if ключ_кэша not in кэш:
кэш[ключ_кэша] = метод(self, *args, **kwargs)
return кэш[ключ_кэша]
return обертка
# Пример класса с декорированными методами
class Пользователь:
def __init__(self, имя, права):
self.имя = имя
self.права = права
@требует_прав("читать")
@кэшировать_метод
def получить_данные(self, идентификатор):
print(f"Загрузка данных для {идентификатор}...")
# Имитация загрузки данных
time.sleep(1)
return f"Данные пользователя {self.имя}, идентификатор {идентификатор}"
@требует_прав("писать")
def сохранить_данные(self, данные):
print(f"Сохранение данных: {данные}")
return "Данные сохранены"
# Создание пользователей с разными правами
админ = Пользователь("Админ", ["читать", "писать"])
гость = Пользователь("Гость", ["читать"])
# Вызов методов с правами
try:
print(админ.получить_данные("123"))
print(админ.получить_данные("123")) # Из кэша
print(админ.сохранить_данные("новые данные"))
except PermissionError as e:
print(f"Ошибка доступа: {e}")
try:
print(гость.сохранить_данные("попытка записи")) # Нет прав
except PermissionError as e:
print(f"Ошибка доступа: {e}")
Создайте декоратор, который ограничивает частоту вызовов функции до одного раза в заданное количество секунд.
import time
import functools
def ограничить_частоту(интервал_секунд):
def декоратор_ограничения(функция):
последний_вызов = 0
@functools.wraps(функция)
def обертка(*args, **kwargs):
nonlocal последний_вызов
текущее_время = time.time()
if текущее_время - последний_вызов < интервал_секунд:
оставшееся_время = интервал_секунд - (текущее_время - последний_вызов)
raise RuntimeError(f"Функцию можно вызывать не чаще чем раз в {интервал_секунд} секунд. Осталось ждать {оставшееся_время:.2f} секунд")
последний_вызов = текущее_время
return функция(*args, **kwargs)
return обертка
return декоратор_ограничения
# Тестирование
@ограничить_частоту(2) # Ограничение: 1 вызов в 2 секунды
def запрос_к_api():
return "Данные от API"
# Попытка частых вызовов
try:
print(запрос_к_api())
print(запрос_к_api()) # Должен вызвать ошибку
except RuntimeError as e:
print(f"Ошибка: {e}")
print("Ждем 2 секунды...")
time.sleep(2)
print(запрос_к_api()) # Должен выполниться успешно
Создайте декоратор, который проверяет, что все аргументы функции являются положительными числами.
import functools
def только_положительные(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
# Проверяем позиционные аргументы
for аргумент in args:
if not isinstance(аргумент, (int, float)) or аргумент <= 0:
raise ValueError(f"Все аргументы должны быть положительными числами. Получено: {аргумент}")
# Проверяем именованные аргументы
for имя, значение in kwargs.items():
if not isinstance(значение, (int, float)) or значение <= 0:
raise ValueError(f"Аргумент '{имя}' должен быть положительным числом. Получено: {значение}")
return функция(*args, **kwargs)
return обертка
# Тестирование
@только_положительные
def вычислить_площадь_прямоугольника(длина, ширина):
return длина * ширина
# Корректные вызовы
print(вычислить_площадь_прямоугольника(5, 3))
print(вычислить_площадь_прямоугольника(длина=4, ширина=6))
# Некорректные вызовы
try:
вычислить_площадь_прямоугольника(-5, 3)
except ValueError as e:
print(f"Ошибка: {e}")
try:
вычислить_площадь_прямоугольника(5, ширина=0)
except ValueError as e:
print(f"Ошибка: {e}")
Создайте декоратор, который преобразует возвращаемое значение функции в строку в верхнем регистре, если это строка.
import functools
def в_верхний_регистр(функция):
@functools.wraps(функция)
def обертка(*args, **kwargs):
результат = функция(*args, **kwargs)
if isinstance(результат, str):
return результат.upper()
return результат
return обертка
# Тестирование
@в_верхний_регистр
def приветствие(имя):
return f"привет, {имя}!"
@в_верхний_регистр
def сложение(a, b):
return a + b
# Вызов функций
print(приветствие("анна")) # Будет в верхнем регистре
print(сложение(5, 3)) # Останется числом
@в_верхний_регистр
def получить_статус():
return "операция выполнена успешно"
print(получить_статус()) # Будет в верхнем регистре