Декораторы в Python: полное руководство для начинающих

Автор: Javohir Abdullayev · · Python

Декораторы в Python: полное руководство для начинающих

Что такое декораторы в Python и зачем они нужны?

Когда вы начинаете писать код на Python, вы часто встречаете такие конструкции, как @property, @login_required (Django) или @dp.message() (Aiogram). Конструкции, начинающиеся с этого символа, называются декораторами (decorators). Многим начинающим разработчикам эта тема может показаться сложной и загадочной. На самом же деле декоратор — это очень удобный инструмент, позволяющий расширить возможности существующей функции (добавить дополнительное поведение), не изменяя её исходный код.

В этом руководстве мы шаг за шагом разберём механизм работы декораторов, начав с самых простых понятий и закрепив всё практическими примерами.

1. Функция — объект первого класса (First-Class Citizen)

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

  • Присваивать функцию переменной;
  • Передавать функцию в качестве аргумента другой функции;
  • Возвращать функцию из другой функции в качестве результата.

Давайте рассмотрим простой пример:

def salom_ber(ism):
    return f"Assalomu alaykum, {ism}!"

# Присваиваем функцию переменной
xabar = salom_ber
print(xabar("Ali"))  # Результат: Assalomu alaykum, Ali!

2. Вложенные функции и возврат функций

Внутри функции можно объявить другую функцию и вернуть её наружу. Это является фундаментом для понимания декораторов:

def tashqi_funksiya():
    def ichki_funksiya():
        return "Ichki funksiyadan salom!"
    return ichki_funksiya

yangi_funksiya = tashqi_funksiya()
print(yangi_funksiya())  # Результат: Ichki funksiyadan salom!

3. Создаём наш первый простой декоратор

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

def mening_dekoratorim(asosiy_funksiya):
    def wrapper():
        print("Funksiya ishga tushishidan oldin bajariladigan kod.")
        asosiy_funksiya()
        print("Funksiya ishlab bo'lgach bajariladigan kod.")
    return wrapper

def oddiy_salom():
    print("Salom, dunyo!")

# Способ декорирования:
bezatilgan_salom = mening_dekoratorim(oddiy_salom)
bezatilgan_salom()

Синтаксический сахар: символ @

Чтобы не писать вручную bezatilgan_salom = mening_dekoratorim(oddiy_salom), в Python предусмотрен специальный символ @:

@mening_dekoratorim
def oddiy_salom():
    print("Salom, dunyo!")

oddiy_salom()

Оба способа дают абсолютно одинаковый результат, но синтаксис с @ выглядит гораздо более читаемым и лаконичным.

4. Декорирование функций, принимающих аргументы

В реальных проектах функции принимают различные аргументы. Чтобы функция-обёртка (wrapper) могла принимать любое количество параметров, используются *args и **kwargs:

def xabar_dekoratori(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] {func.__name__} funksiyasi ishga tushirildi.")
        natija = func(*args, **kwargs)
        print(f"[LOG] Funksiya o'z ishini yakunladi.")
        return natija
    return wrapper

@xabar_dekoratori
def hisobla(a, b):
    return a + b

javob = hisobla(10, 25)
print(f"Natija: {javob}")

5. Практический пример: измерение времени выполнения

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

import time

def vaqtni_olchash(func):
    def wrapper(*args, **kwargs):
        boshlanish = time.time()
        natija = func(*args, **kwargs)
        tugash = time.time()
        sarflangan_vaqt = tugash - boshlanish
        print(f"{func.__name__} funksiyasi {sarflangan_vaqt:.4f} soniyada bajarildi.")
        return natija
    return wrapper

@vaqtni_olchash
def katta_ro‘yxat_yaratish():
    return [x ** 2 for x in range(1_000_000)]

katta_ro‘yxat_yaratish()

6. Зачем нужен functools.wraps?

Когда мы оборачиваем функцию декоратором, метаданные исходной функции (её имя, docstring) перезаписываются данными функции-обёртки wrapper. Чтобы предотвратить это, на помощь приходит functools.wraps из стандартной библиотеки Python:

from functools import wraps

def xavfsiz_dekorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Bu wrapper funksiya."""
        return func(*args, **kwargs)
    return wrapper

@xavfsiz_dekorator
def foydalanuvchi_info():
    """Bu funksiya foydalanuvchi haqida ma'lumot beradi."""
    pass

print(foydalanuvchi_info.__name__)  # foydalanuvchi_info
print(foydalanuvchi_info.__doc__)   # Bu funksiya foydalanuvchi haqida ma'lumot beradi.

Всегда берите за правило использовать @wraps(func) при написании собственных декораторов. Это поможет избежать путаницы при отладке и написании автоматических тестов.

Заключение

Декораторы в Python — это мощный инструмент для поддержания чистоты кода (принцип DRY), логирования, контроля доступа и аутентификации в единой точке. Освоив базовые принципы из этого руководства, вам станет значительно проще и понятнее работать с такими популярными библиотеками и фреймворками, как Django, Flask, FastAPI или Aiogram.

Теги: #Python #Dekoratorlar #Backend #Dasturlash Asoslari