Объединение декораторов Python с аргументами в один

Я использую два разных декоратора из двух разных библиотек. Допустим: @decorator1(param1, param2) и @decorator2(param3, param4). То, что я часто использую во многих функциях как:

from moduleA import decorator1
from moduleB import decorator2

@decorator2(foo='param3', bar='param4')
@decorator1(name='param1', state='param2')
def myfunc(funcpar1, funcpar2):
    ...

Поскольку это происходит каждый раз, я хотел бы создать пользовательский декоратор, который обрабатывает их оба. Что-то вроде:

@mycustomdecorator(name='param1', state='param2',
                   foo='param3', bar='param4')
def myfunc(funcpar1, funcpar2):
    ...

Как мне это достичь?

Ответ 1

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

Однако, если вы действительно этого хотите, вы можете сделать это следующим образом:

import functools

from moduleA import decorator1
from moduleB import decorator2

def my_decorator(foo, bar, name, state):
    def inner(func):
        @decorator2(foo=foo, bar=bar)
        @decorator1(name=name, state=state)
        @functools.wraps(func)  # Not required, but generally considered good practice
        def newfunc(*args, **kwargs)
            return func(*args, **kwargs)
        return newfunc
    return inner

@my_decorator(foo='param3', bar='param4', name='param1', state='param2')
def myfunc(funcpar1, funcpar2):
    ...

Исходя из комментариев, здесь, альтернативный метод:

def my_decorator(foo, bar, name, state):
    def inner(func):
        # Please note that for the exact same result as in the other one, 
        # the order of decorators has to be reversed compared to normal decorating
        newfunc = decorator1(name=name, state=state)(func)
        newfunc = decorator2(foo=foo, bar=bar)(newfunc)
        # Note that functools.wraps shouldn't be required anymore, as the other decorators should do that themselves
        return newfunc
    return inner

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

Это просто на самом деле - просто напишите декоратор, который возвращает еще один декоратор, который будет иметь внутреннюю функцию, украшенную двумя другими декораторами;)

Также может быть хорошей идеей использовать functools.wraps, ради хороших привычек. Это стандартная библиотека и много помогает при использовании отладки и интерактивной консоли: https://docs.python.org/3.7/library/functools.html

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

Ответ 2

Это просто простая функция, в которой decorator1 и decorator2 возвращают функции, которые вы хотите скомпоновать. Настоящая работа может быть абстрагированы в функции compose.

# In the special case of composing decorators, the lambda expression
# only needs to be defined with a single argument, like
#
#   lambda func: f(g(func))
#
# but I show a more general form.
def compose(f, g):     
    return lambda *args, **kwargs: f(g(*args, **kwargs))

def customdecorator(foo, bar, name, state):
    return compose(decorator2(foo=foo, bar=bar),
                   decorator1(name=name, state=state))

Ответ 3

Декоратор - это не что иное, как синтаксический сахар для синтаксиса, например func = decorator(func).

Таким образом, вы можете легко сделать свой собственный декоратор, который сделает все, что вам нужно, с помощью следующего синтаксиса:

def mycustomdecorator(foo, bar, name, state)
    def innerdecorator(func):
        func = decorator1(foo=foo, bar=bar)(func)
        func = decorator2(name=name, state=state)(func)
        return func
    return innerdecorator

После этого вы сможете использовать @mycustomdecorator без лишних хлопот. Дайте мне знать, если это сработает, я не тестировал его, но теоретически это должно было бы.

Какая там магия: во-первых, нам нужно получить параметры для наших декораторов. Таким образом, мы можем передать их вложенную функцию. Затем мы принимаем нашу функцию в качестве аргумента, а последнее, мы получаем параметры для нашей функции. Мы можем вложить наше решение так сильно, как нам нужно.