Вложенные декораторы в Python, определяющие методы моделирования

Допустим, у нас есть три метода моделирования:

def method1(func):
    def wrapper(*args, **kwargs):
        #Implementation of some simulator backend
        #but as a toy model we just pass a string here
        return func(*args, simulation_method='method1', **kwargs)
    return wrapper


def method2(func):
    def wrapper(*args, **kwargs):
        #Implementation of some simulator backend
        #but as a toy model we just pass a string here
        return func(*args, simulation_method='method2', **kwargs)
    return wrapper


def method3(func):
    def wrapper(*args, **kwargs):
        #Implementation of some simulator backend
        #but as a toy model we just pass a string here
        return func(*args, simulation_method='method3', **kwargs)
    return wrapper

Так что мы можем вызвать функцию simulation с помощью определенного метода через

@method3
def simulation(simulation_method):
    #Implementation of some computation that needs to be simulated
    #but as a toy model we just print the following statement:
    print(f"Running simulation with {simulation_method} method")

Что дает результат

"Running simulation with method3 method"

Теперь я хочу определить декоратор с именем MultiSimulation, который многократно вызывает функцию моделирования при использовании заданных методов моделирования со следующим синтаксисом:

@MultiSimulation
@method1
@method2
@method3
def simulation(simulation_method):
    print(f"Running simulation with {simulation_method} method")

Это должно дать результат:

"Running simulation with method1 method"
"Running simulation with method2 method"
"Running simulation with method3 method"

Я застрял с определением MultiSimulation и был бы рад получить здесь помощь. Спасибо!

Я пробовал разные варианты, такие как

def MultiSimulation(func):
    def repeated_simulation(*args, **kwargs):
        simulation_methods = []
        if hasattr(func, '__wrapped__'):
            simulation_methods = func.__wrapped__.simulation_methods
        result = None
        for simulation_method in simulation_methods:
            kwargs['simulation_method'] = simulation_method
            result = func(*args, **kwargs)
        return result
    repeated_simulation.simulation_methods = []
    repeated_simulation.__wrapped__ = func
    return repeated_simulation

Но я не получаю никакого вывода.

Вы согласны сменить декораторов?

Dorian Turba 14.02.2023 15:04
Потяните за рычаг выброса энергососущих проектов
Потяните за рычаг выброса энергососущих проектов
На этой неделе моя команда отменила проект, над которым я работал. Неделя усилий пошла насмарку.
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
1
1
73
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Когда вы укладываете декораторы, обратите внимание, что они декорируются снизу вверх. Это означает, что method3 украшает simulation, а method2 украшает «эту декорированную функцию», а не саму simulation. Но, как вы показали в своем вопросе, вам нужно «повторить» функцию с разными декораторами. Конечно, есть способы сделать это, но я бы предпочел не делать этого таким образом.

Вместо этого вы можете передать свои методы моделирования MultiSimulation, например:

@MultiSimulation(method1, method2, method3)

Вот реализация:

def method1(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method = "method1", **kwargs)
    return wrapper

def method2(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method = "method2", **kwargs)
    return wrapper

def method3(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method = "method3", **kwargs)
    return wrapper

def MultiSimulation(*simulation_methods):
    def decorator(fn):
        def inner(*args, **kwargs):
            return [m(fn)(*args, **kwargs) for m in simulation_methods]
        return inner
    return decorator

@MultiSimulation(method1, method2, method3)
def simulation(simulation_method):
    print(f"Running simulation with {simulation_method} method")

simulation()

Выход:

Running simulation with method1 method
Running simulation with method2 method
Running simulation with method3 method

Я предпочитаю этот стиль стеку декоратора.

Dorian Turba 14.02.2023 15:14

Спасибо за этот ответ. Я должен согласиться с Дорианом Турба, что этот подход лучше, чем то, что я имел в виду изначально! Я новичок в использовании декораторов и узнал от вас кое-что новое.

LinLin 14.02.2023 15:20

@LinLin Добавлено объяснение, почему вам нужно что-то подобное.

S.B 14.02.2023 15:24

Мне не нравится использовать произвольное количество декораторов, так как это приводит к беспорядку и порядку при их размещении. Здесь я бы использовал декоратор на основе классов, который поддерживает несколько декораторов в своем конструкторе:

class MultiSimulation:
    def __init__(self, *methods):
        self.methods = methods

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            return [method(func)(*args, **kwargs) for method in self.methods]
        return wrapper


def method1(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method='method1', **kwargs)
    return wrapper


def method2(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method='method2', **kwargs)
    return wrapper


def method3(func):
    def wrapper(*args, **kwargs):
        return func(*args, simulation_method='method3', **kwargs)
    return wrapper

@MultiSimulation(method1, method2, method3)
def simulation(simulation_method):
    print(f"Running simulation with {simulation_method} method")

simulation()

Запуск этого кода дает:

"Running simulation with method1 method"
"Running simulation with method2 method"
"Running simulation with method3 method"

Спасибо. Я очень ценю ваше решение, которое приводит к тому же синтаксису, что и в ответе С.Б. Построение на основе классов выглядит очень интересно! Я также предпочитаю этот подход тому, что я изначально имел в виду.

LinLin 14.02.2023 15:23
Ответ принят как подходящий

Требуется доработка декоратора, чтобы сохранить стек декораторов.

С переработкой вот что вы можете получить:

@MultiSimulation
@method1
@method2
@method3
def simulation(simulation_method):
    print(f"Running simulation with {simulation_method} method")
    return simulation_method

print(simulation())
# Running simulation with method1 method
# Running simulation with method2 method
# Running simulation with method3 method
# ['method1', 'method2', 'method3']

Вам нужно обновить декораторы следующим образом:

def method1(func):
    def wrapper1(*args, simulation_method = "method1", **kwargs):
        return func(*args, simulation_method=simulation_method, **kwargs)

    return wrapper1

И вам нужен этот декоратор:

def MultiSimulation(func):
    def repeated_simulation(*args, **kwargs):
        tmp_fct = func
        results = []
        while tmp_fct:
            try:
                results.append(tmp_fct(*args, **kwargs))
            except TypeError:
                pass
            try:
                tmp_fct = tmp_fct.__closure__[0].cell_contents
            except TypeError:
                break
        return results

    return repeated_simulation

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

def method1(func):
    def wrapper1(*args, simulation_method = "method1", **kwargs):
        return func(*args, simulation_method=simulation_method, **kwargs)

    return wrapper1


def method2(func):
    def wrapper2(*args, simulation_method = "method2", **kwargs):
        return func(*args, simulation_method=simulation_method, **kwargs)

    return wrapper2


def method3(func):
    def wrapper3(*args, simulation_method = "method3", **kwargs):
        return func(*args, simulation_method=simulation_method, **kwargs)

    return wrapper3


def MultiSimulation(func):
    def repeated_simulation(*args, **kwargs):
        tmp_fct = func
        results = []
        while tmp_fct:
            try:
                results.append(tmp_fct(*args, **kwargs))
            except TypeError:
                pass
            try:
                tmp_fct = tmp_fct.__closure__[0].cell_contents
            except TypeError:
                break
        return results

    return repeated_simulation


@MultiSimulation
@method1
@method2
@method3
def simulation(simulation_method):
    print(f"Running simulation with {simulation_method} method")
    return simulation_method


print(simulation())
# Running simulation with method1 method
# Running simulation with method2 method
# Running simulation with method3 method
# ['method1', 'method2', 'method3']

Другие вопросы по теме

Почему я получаю сообщение об ошибке при использовании декоратора с моей функцией факториала?
Python украшает `class.method`, который изменяет `class.self`
Почему я получаю «отсутствует обязательный аргумент: self» при использовании декоратора, написанного как класс с методом?
Почему мой менеджер контекста не завершает работу при исключении
Как добавить дополнительный оператор печати для каждого оператора печати, вызываемого в моем коде? (желательно с помощью декораторов кода)
Как указать тип функции, добавленной в класс декоратором класса в Python
Как получить поддержку ввода для статического свойства (с помощью декоратора)
Сохранение аргументов функции при каждом ее вызове с помощью декоратора, определенного в родительском классе.
AtributeError: невозможно установить атрибут для свойства списка python
Как выполнить метод класса из функции декоратора