Декораторы в Python: расширяем функциональность без изменения кода

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

РазработкаPython

6 мин

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

Что такое декораторы

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

def my_decorator(func):
    def wrapper():
        print("Что-то происходит до вызова функции")
        func()
        print("Что-то происходит после вызова функции")
    return wrapper

@my_decorator
def say_hello():
    print("Привет!")

say_hello()

Результат выполнения:

Что-то происходит до вызова функции
Привет!
Что-то происходит после вызова функции

Символ @ — это синтаксический сахар Python. Запись @my_decorator перед функцией эквивалентна say_hello = my_decorator(say_hello).

Декораторы с аргументами

Но что делать, если наша функция принимает параметры? Для этого используем *args и **kwargs:

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

@repeat(times=3)
def greet(name):
    print(f"Привет, {name}!")

greet("Алексей")

Вывод:

Привет, Алексей!
Привет, Алексей!
Привет, Алексей!

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

Практические примеры декораторов

Измерение времени выполнения

import time
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"Функция {func.__name__} выполнялась {end - start:.4f} секунд")
        return result
    return wrapper

@timer
def heavy_computation():
    time.sleep(2)
    return "Готово"

heavy_computation()

Обратите внимание на @wraps(func) — это встроенный декоратор из модуля functools, который сохраняет метаданные оригинальной функции (имя, документацию и так далее).

Кэширование результатов

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # Выполнится мгновенно благодаря кэшированию

Проверка прав доступа

def require_auth(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated'):
            raise PermissionError("Требуется авторизация")
        return func(user, *args, **kwargs)
    return wrapper

@require_auth
def delete_account(user, account_id):
    print(f"Аккаунт {account_id} удалён")

user = {'is_authenticated': True, 'username': 'admin'}
delete_account(user, 123)

Цепочки декораторов

Декораторы можно комбинировать, применяя несколько к одной функции:

@timer
@memoize
def complex_calculation(x, y):
    time.sleep(1)
    return x ** y

# Первый вызов займёт ~1 секунду
complex_calculation(2, 10)

# Второй вызов будет мгновенным (результат закэширован)
complex_calculation(2, 10)

Декораторы применяются снизу вверх, то есть сначала memoize, затем timer.

Классы как декораторы

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

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Вызов #{self.count} функции {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def process_data():
    print("Обработка данных...")

process_data()
process_data()
process_data()

Встроенные декораторы Python

Python предоставляет несколько полезных встроенных декораторов:

  • @property — превращает метод в атрибут класса

  • @staticmethod — создаёт статический метод

  • @classmethod — создаёт метод класса

  • @functools.lru_cache — кэширование с ограничением размера

  • @dataclass — автоматическое создание методов класса

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_function(param):
    # Сложные вычисления
    return param * 2

Когда использовать декораторы

Декораторы идеально подходят для задач, которые нужно применять к множеству функций: логирование, валидация входных данных, обработка исключений, контроль доступа, кэширование, измерение производительности, retry-логика при ошибках сети. Они помогают соблюдать принцип DRY (Don't Repeat Yourself) и делают код чище и понятнее.

Потенциальные проблемы

Несмотря на всю мощь декораторов, нужно помнить о нескольких моментах. Излишнее использование декораторов может усложнить отладку кода. Декораторы добавляют небольшие накладные расходы на производительность. Важно всегда использовать @wraps для сохранения метаданных функции. Цепочки из множества декораторов могут снижать читаемость кода.

Резюме

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

Хотите освоить декораторы и другие продвинутые возможности Python на практике?

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

А если у вас возникнут вопросы в процессе обучения — добро пожаловать в наш Telegram-канал! Это живое сообщество разработчиков, где можно задать любой вопрос и получить развёрнутый ответ. Атмосфера у нас дружная и познавательная — каждый день мы разбираем топовые темы в разработке, делимся опытом и помогаем друг другу расти. Присоединяйтесь к Кодику — начните свой путь в программировании с теми, кто действительно понимает, как сделать обучение эффективным и интересным!

Комментарии