Серия Python — Часть 19

Декораторы Python — это мощная функция языка, позволяющая изменять поведение функций или классов. Они позволяют добавлять функциональные возможности в существующий код без изменения его структуры. В этой статье мы подробно рассмотрим декораторы и узнаем, как их эффективно использовать и создавать.

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

В Python декораторы — это специальные функции, которые оборачивают другие функции или классы, позволяя изменять их поведение. Они следуют определенному синтаксису, используя символ @, за которым следует имя декоратора, которое помещается над определением функции или класса. Когда вызывается декоративная функция или класс, функция декоратора вызывается автоматически.

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

Использование декораторов

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

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

В приведенном выше примере мы определили функцию-декоратор log_decorator, которая принимает функцию func в качестве аргумента. Внутри декоратора мы определяем вложенную функцию wrapper, которая отвечает за добавление функции ведения журнала. Затем декоратор возвращает функцию wrapper.

Функция wrapper принимает любое количество позиционных и ключевых аргументов, используя *args и **kwargs соответственно. Он регистрирует сообщение перед вызовом исходной функции func и возвращает результат.

Чтобы применить log_decorator к функции greet, мы используем синтаксис @ и размещаем его над определением функции. Когда вызывается greet, декоратор вызывается автоматически, и перед выполнением функции печатается сообщение журнала.

Вывод приведенного выше кода будет:

Calling function: greet
Hello, Alice!

Как видите, декоратор добавил функциональность логирования без изменения исходного кода функции greet.

Уроки декорирования

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

import time

def timing_decorator(method):
    def wrapper(self, *args, **kwargs):
        start_time = time.time()
        result = method(self, *args, **kwargs)
        end_time = time.time()
        print(f"Execution time: {end_time - start_time} seconds")
        return result
    return wrapper

class Calculator:
    def __init__(self):
        self.total = 0

    @timing_decorator
    def add(self, x, y):
        self.total = x + y
        return self.total

calculator = Calculator()
result = calculator.add(5, 7)
print(f"Result: {result}")

В этом примере мы определили функцию-декоратор timing_decorator, которая принимает метод method в качестве аргумента. Декоратор создает функцию wrapper, которая измеряет время выполнения метода. Время начала и окончания записывается с помощью функции time.time(), а продолжительность распечатывается.

Функция wrapper принимает self в качестве первого аргумента, так как это метод класса. Он также принимает любое количество позиционных и ключевых аргументов. Внутри функции wrapper мы вызываем исходный метод method, передавая self, *args и **kwargs. После выполнения метода декоратор печатает время выполнения.

Чтобы применить timing_decorator к методу add класса Calculator, мы используем синтаксис @ и размещаем его над определением метода. При вызове add автоматически вызывается декоратор, измеряющий время выполнения.

Вывод приведенного выше кода будет примерно таким:

Execution time: 0.000123 seconds
Result: 12

Как видите, декоратор добавил в метод add функциональные возможности измерения времени, измеряя время выполнения без изменения исходного кода.

Создание декораторов

Теперь, когда мы увидели, как использовать декораторы, давайте рассмотрим, как создавать собственные декораторы. Декораторы — это просто функции, которые принимают другую функцию (или класс) в качестве аргумента и возвращают ее измененную версию.

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

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_decorator
def greet(name):
    return f"Hello, {name}!"

result = greet("Alice")
print(result)

В этом примере мы определили функцию-декоратор uppercase_decorator, которая принимает функцию func в качестве аргумента. Внутри декоратора мы определяем функцию wrapper, которая вызывает исходную функцию и преобразует ее результат в верхний регистр с помощью метода upper().

Измененный результат затем возвращается декоратором. При вызове greet автоматически вызывается декоратор, преобразующий приветственное сообщение в верхний регистр.

Вывод приведенного выше кода будет:

HELLO, ALICE!

Как видите, декоратор изменил результат функции greet, преобразовав его в верхний регистр.

Обработка аргументов в декораторах

Декораторы могут обрабатывать различные типы аргументов, передаваемых декорируемой функции или классу. Есть три общих шаблона для обработки аргументов в декораторах:

  • Декораторы без аргументов
  • Декораторы с аргументами
  • Декораторы, которые можно использовать с аргументами или без них

Давайте рассмотрим каждый шаблон на примерах.

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

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

def message_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function execution")
        result = func(*args, **kwargs)
        print("After function execution")
        return result
    return wrapper

@message_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

В этом примере message_decorator не принимает никаких дополнительных аргументов. Функция wrapper просто выводит сообщение до и после вызова исходной функции.

Вывод приведенного выше кода будет:

Before function execution
Hello, Alice!
After function execution

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

Декораторы также могут принимать аргументы, что позволяет настраивать их поведение. Вот пример декоратора, который повторяет выполнение функции заданное количество раз:

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

@repeat_decorator(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

В этом примере мы определили функцию-декоратор repeat_decorator, которая принимает аргумент repeats. Этот аргумент представляет количество раз, которое декорированная функция будет выполняться. Функция repeat_decorator возвращает другую функцию-декоратор decorator, которая принимает целевую функцию func в качестве аргумента.

Внутри функции wrapper мы используем цикл для повторения выполнения исходной функции repeats раз. После цикла возвращается результат последнего выполнения.

Чтобы применить repeat_decorator с определенным количеством повторений к функции greet, мы используем синтаксис @ и указываем аргумент в круглых скобках. В данном случае @repeat_decorator(3) означает, что функция greet будет выполнена три раза.

Вывод приведенного выше кода будет:

Hello, Alice!
Hello, Alice!
Hello, Alice!

Как видите, декоратор трижды повторил выполнение функции greet.

Декораторы, которые можно использовать с аргументами или без них

Также можно создавать декораторы, которые можно использовать с дополнительными аргументами или без них. Вот пример декоратора, который добавляет префикс к выходным данным функции с необязательным аргументом префикса:

def prefix_decorator(prefix=None):
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            if prefix:
                return f"{prefix} {result}"
            return result
        return wrapper
    return decorator

@prefix_decorator
def greet(name):
    return f"Hello, {name}!"

@prefix_decorator("Goodbye")
def farewell(name):
    return f"Goodbye, {name}!"

result1 = greet("Alice")
result2 = farewell("Bob")
print(result1)
print(result2)

В этом примере функция prefix_decorator предназначена для обработки обоих случаев: с аргументом или без него. Если аргумент не указан, префикс будет None. Внутри функции wrapper мы проверяем, имеет ли prefix значение, и если да, то добавляем его к результату.

При применении prefix_decorator без аргумента, как в @prefix_decorator, префикс будет None. При применении его с аргументом, как в @prefix_decorator("Goodbye"), будет использоваться указанный префикс.

Вывод приведенного выше кода будет:

Hello, Alice!
Goodbye Bob!

Как видите, декоратор добавил указанный префикс к выводу функции farewell, оставив вывод функции greet без изменений.

Заключение

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

В этой статье мы изучили основы декораторов, как использовать их для изменения функций и классов и как создавать свои собственные декораторы. Мы также рассмотрели различные шаблоны для обработки аргументов в декораторах. Обладая этими знаниями, вы можете начать использовать возможности декораторов в своих собственных проектах Python.

Декораторы — обширная тема с многочисленными возможностями и вариантами использования. Продолжая свое знакомство с Python, не стесняйтесь изучать более продвинутые концепции и методы, связанные с декораторами.

LinkedIn, Medium, Instagram, Kaggle и GitHub.

Если вам нравится читать истории, подобные этой, и вы хотите поддержать меня, подумайте о том, чтобы стать участником Medium. Взяв 5 долларов в месяц, вы открываете неограниченный доступ к историям на Medium. Если вы воспользуетесь моей ссылкой для регистрации, я получу небольшую комиссию.

Уже участник? Подпишитесь, чтобы получать уведомления, когда я опубликую.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .