Что такое декоратор в Python: первое знакомство

Что такое декоратор в Python: первое знакомство Декораторы

Привет!

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

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

В этой статье мы разберём:

  • Что такое декораторы
  • Зачем они нужны
  • Как работают замыкания (closures)
  • Простые примеры декораторов

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный Python
  • Базовое понимание функций (создание, вызов, передача в качестве аргументов)

Совет: Декораторы — это продвинутая тема. Не расстраивайся, если не поймёшь всё сразу. С практикой придёт понимание.

Основная часть

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

Декоратор — это функция, которая принимает другую функцию в качестве аргумента и возвращает новую функцию с расширенным поведением.

Простая аналогия:
Представь, что у тебя есть простая функция say_hello(). Ты хочешь, чтобы перед её выполнением всегда печаталась строка «Начинаем работу». Вместо того чтобы менять код say_hello(), ты создаёшь декоратор, который оборачивает её в дополнительную логику.

Функции высшего порядка и замыкания

Прежде чем погрузиться в декораторы, давай вспомним два важных понятия:

  1. Функции высшего порядка — функции, которые принимают другие функции как аргументы или возвращают их.
    def greet(name):
        return f"Привет, {name}!"
    
    def run_function(func, arg):
        return func(arg)
    
    result = run_function(greet, "Анна")
    print(result)   # Привет, Анна!
    1. Замыкания (closures) — функция, которая «запоминает» переменные из внешней области, даже после того как внешняя функция завершила работу.
    def make_multiplier(factor):
        def multiplier(x):
            return x * factor
        return multiplier
    
    double = make_multiplier(2)
    print(double(5))   # 10

    Совет: Замыкания — основа декораторов. Они позволяют декоратору «запомнить» исходную функцию.

    Простой декоратор (без синтаксиса @)

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

    def logger(func):
        def wrapper(*args, **kwargs):
            print(f"Вызов функции: {func.__name__}")
            result = func(*args, **kwargs)
            print(f"Результат: {result}")
            return result
        return wrapper
    
    # Применяем декоратор вручную
    def add(a, b):
        return a + b
    
    add_with_logging = logger(add)
    add_with_logging(3, 5)
    
    # Вывод:
    # Вызов функции: add
    # Результат: 8

    Как это работает:

    1. logger принимает функцию func
    2. Внутри logger создаётся функция wrapper, которая обёртывает вызов func
    3. wrapper добавляет дополнительное поведение (логирование)
    4. logger возвращает wrapper

    Декоратор с синтаксисом @

    В Python есть специальный синтаксис для применения декораторов — символ @.

    def logger(func):
        def wrapper(*args, **kwargs):
            print(f"Вызов функции: {func.__name__}")
            result = func(*args, **kwargs)
            print(f"Результат: {result}")
            return result
        return wrapper
    
    @logger
    def add(a, b):
        return a + b
    
    add(3, 5)
    
    # Вывод:
    # Вызов функции: add
    # Результат: 8

    Совет: @logger эквивалентно add = logger(add). Просто короче и красивее.

    Декоратор для замера времени выполнения

    import time
    
    def timer(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 slow_function():
        time.sleep(1)
        return "Готово!"
    
    slow_function()
    
    # Вывод:
    # Функция slow_function выполнилась за 1.0012 секунд

    Совет: Это очень полезный декоратор для оптимизации кода.

    Декоратор для проверки прав доступа

    def require_admin(func):
        def wrapper(user, *args, **kwargs):
            if not user.get("is_admin", False):
                print("Доступ запрещён: требуется права администратора")
                return None
            return func(user, *args, **kwargs)
        return wrapper
    
    @require_admin
    def delete_user(user, user_id):
        print(f"Пользователь {user_id} удалён")
    
    admin_user = {"name": "admin", "is_admin": True}
    regular_user = {"name": "alice", "is_admin": False}
    
    delete_user(admin_user, 123)   # Пользователь 123 удалён
    delete_user(regular_user, 456) # Доступ запрещён: требуется права администратора

    Декоратор с несколькими функциями

    def logger(func):
        def wrapper(*args, **kwargs):
            print(f"Вызов функции: {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    
    @logger
    def greet(name):
        return f"Привет, {name}!"
    
    @logger
    def add(a, b):
        return a + b
    
    print(greet("Анна"))   # Вызов функции: greet
    print(add(3, 5))      # Вызов функции: add

    Задачи для закрепления

    Задача 1. Напиши декоратор, который выводит «Начало работы» и «Конец работы» до и после вызова функции.

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

    Задача 3. Напиши декоратор, который проверяет, что пользователь является администратором.

    Задача 4. Напиши декоратор, который выводит аргументы функции перед её вызовом.

    Задача 5. Что произойдёт, если декоратор не возвращает функцию?

    Ответы:

    Задача 1.

    def border(func):
        def wrapper(*args, **kwargs):
            print("Начало работы")
            result = func(*args, **kwargs)
            print("Конец работы")
            return result
        return wrapper
    
    @border
    def say_hello():
        print("Привет, мир!")
    
    say_hello()
    # Начало работы
    # Привет, мир!
    # Конец работы

    Задача 2.

    import time
    
    def timer(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"Время: {end - start:.4f} сек")
            return result
        return wrapper
    
    @timer
    def slow():
        time.sleep(0.5)
        return "Готово"
    
    slow()

    Задача 3.

    def require_admin(func):
        def wrapper(user, *args, **kwargs):
            if not user.get("is_admin", False):
                return "Доступ запрещён"
            return func(user, *args, **kwargs)
        return wrapper
    
    @require_admin
    def delete_user(user, user_id):
        return f"Пользователь {user_id} удалён"

    Задача 4.

    def debug(func):
        def wrapper(*args, **kwargs):
            print(f"Аргументы: {args}, {kwargs}")
            return func(*args, **kwargs)
        return wrapper

    Задача 5.

    # Если декоратор не возвращает функцию, исходная функция будет заменена на None.

    Нюансы и подводные камни

    Сохранение метаданных функции

    Декоратор изменяет имя функции:

    def logger(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
    @logger
    def greet():
        pass
    
    print(greet.__name__)   # wrapper (а не greet)

    Как исправить: Используй functools.wraps.

    from functools import wraps
    
    def logger(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

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

    Простой декоратор без аргументов:

    @logger
    def func():
        pass

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

    @logger(level="DEBUG")
    def func():
        pass

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

    @timer
    @logger
    def my_func():
        pass

    Частые ошибки и как их избежать

    Ошибка 1: Забыл возвращать функцию

    Всегда возвращай внутреннюю функцию.

    Ошибка 2: Неправильное использование *args и **kwargs

    Всегда используй *args и **kwargs для поддержки любых аргументов.

    Ошибка 3: Потеря метаданных

    Используй functools.wraps для сохранения имени и докстринга.

    Шпаргалка

    Что нужноКак пишется
    Базовый декораторdef decorator(func): def wrapper(*args, **kwargs): ... return wrapper
    С сохранением метаданныхfrom functools import wraps
    Применение декоратора@decorator
    Цепочка декораторов@deco1 @deco2

    Заключение / Выводы

    Сегодня мы:

    • Узнали, что такое декораторы
    • Разобрали, как они работают
    • Написали простые декораторы
    • Познакомились с замыканиями

    КВИЗ

    Что дальше?

    Оцените статью
    IMI-DS - PYTHON LERNEN
    Содержание
    Оглавление ×