Как можно применить декоратор к нескольким функциям одновременно?

Применить один и тот же декоратор к нескольким функциям можно несколькими способами:
  1. Присваивание: Создать декоратор и затем присвоить его каждой функции.
    
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("До вызова функции.")
            result = func(*args, **kwargs)
            print("После вызова функции.")
            return result
        return wrapper
    
    def func1():
        print("Функция 1")
    
    def func2():
        print("Функция 2")
    
    func1 = my_decorator(func1)
    func2 = my_decorator(func2)
          
  2. Цикл: Использовать цикл для итерации по списку функций и применения декоратора.
    
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("До вызова функции.")
            result = func(*args, **kwargs)
            print("После вызова функции.")
            return result
        return wrapper
    
    def func1():
        print("Функция 1")
    
    def func2():
        print("Функция 2")
    
    functions = [func1, func2]
    for i, func in enumerate(functions):
        functions[i] = my_decorator(func)
          
  3. Создание декоратора, применяющего декоратор к списку функций: Создать "мета-декоратор", который принимает список функций и применяет к ним другой декоратор.
    
    def apply_decorator_to_functions(decorator):
        def apply(functions):
            for i, func in enumerate(functions):
                functions[i] = decorator(func)
            return functions
        return apply
    
    
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("До вызова функции.")
            result = func(*args, **kwargs)
            print("После вызова функции.")
            return result
        return wrapper
    
    def func1():
        print("Функция 1")
    
    def func2():
        print("Функция 2")
    
    apply_my_decorator = apply_decorator_to_functions(my_decorator)
    funcs = apply_my_decorator([func1, func2])
    
    # Теперь func1 и func2 задекорированы (но не перезаписаны в глобальной области видимости)
          
  4. Использование библиотеки functools.wraps (рекомендовано): для сохранения метаданных исходных функций. При использовании циклов или apply, нужно обернуть inner wrapper в wraps.

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

1. Применение декоратора непосредственно к нескольким функциям подряд:

    
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Выполняется код до функции.")
        result = func(*args, **kwargs)
        print("Выполняется код после функции.")
        return result
    return wrapper

@my_decorator
def function_one():
    print("Функция первая")

@my_decorator
def function_two():
    print("Функция вторая")

function_one()
function_two()
    
  

Это самый простой и очевидный способ. Мы просто ставим @my_decorator над каждой функцией, которую хотим декорировать. Этот подход хорошо работает, если функций немного.

2. Использование цикла (при определенных обстоятельствах):

Этот метод применим, когда функции хранятся в списке или другой итерируемой структуре. Не всегда возможно или желательно так делать, но иногда это полезно:

    
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Выполняется код до функции.")
        result = func(*args, **kwargs)
        print("Выполняется код после функции.")
        return result
    return wrapper

def function_one():
    print("Функция первая")

def function_two():
    print("Функция вторая")


functions = [function_one, function_two]

for i in range(len(functions)):
    functions[i] = my_decorator(functions[i])

function_one()
function_two()
    
  

Обратите внимание, что в этом случае мы присваиваем результат декорирования обратно элементам списка. Это изменяет исходный список функций. Также крайне важно выполнять это *вне* определения самих функций. Изменение глобальной области видимости внутри определения может привести к неожиданным результатам.

3. Создание класса-декоратора:

Классы-декораторы позволяют создавать более сложные декораторы, которые могут сохранять состояние или иметь параметризацию. Хотя они не обязательно упрощают применение к нескольким функциям одновременно, они обеспечивают лучшую организацию кода, особенно для сложных сценариев. Синтаксис применения остается тем же: @MyClassDecorator.

    
class MyDecorator:
    def __init__(self, message):
        self.message = message

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"Сообщение декоратора: {self.message}")
            result = func(*args, **kwargs)
            return result
        return wrapper

@MyDecorator("Привет от декоратора!")
def my_function():
    print("Функция выполняется.")

my_function()
    
  

4. (Продвинутый) Создание функции для группового декорирования:

Можно создать функцию, которая принимает декоратор и список функций и применяет декоратор ко всем функциям в списке:

    
def decorate_multiple(decorator, functions):
    for func in functions:
        globals()[func.__name__] = decorator(func)  # Модифицирует глобальную область видимости

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Код до функции")
        result = func(*args, **kwargs)
        print("Код после функции")
        return result
    return wrapper

def function_one():
    print("Функция первая")

def function_two():
    print("Функция вторая")

functions_to_decorate = [function_one, function_two]
decorate_multiple(my_decorator, functions_to_decorate)


function_one()
function_two()
    
  

Внимание! Этот метод использует globals() для изменения глобальной области видимости. Это может быть небезопасным и не рекомендуется в большинстве случаев, особенно в больших проектах, так как затрудняет отладку и понимание кода. Лучше избегать изменения глобальной области видимости таким образом.

5. (Продвинутый) Использование метаклассов:

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

В заключение, самый простой и распространенный способ – просто применить декоратор непосредственно к каждой функции, используя синтаксис @decorator_name. Более сложные подходы, такие как использование циклов или функций для группового декорирования, следует использовать с осторожностью и только тогда, когда это действительно необходимо, чтобы избежать усложнения кода и потенциальных проблем с областью видимости.

0