将带参数的Python装饰器合并为单个装饰器

将带参数的Python装饰器合并为单个装饰器,python,python-decorators,Python,Python Decorators,我正在使用来自两个不同库的两个不同的装饰器。比如说:@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 myfun

我正在使用来自两个不同库的两个不同的装饰器。比如说:
@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):
    ...

我该如何做到这一点呢?

我认为你不应该这样做——使用装饰器的原始名称可以提供更好的可读性

但是,如果您真的想这样做,您可以这样做:

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经验的人习惯于用@来应用装饰器,即使仅仅出于这个原因,我更喜欢我的第一个选择。我知道第一次阅读这段代码并理解它的功能需要花三倍的时间

这真的很简单——只需编写一个decorator,返回另一个decorator,该decorator将使用其他两个decorator对其内部函数进行修饰;)

出于良好习惯的考虑,使用functools.wrapps可能也是一个好主意。它是标准库,在调试和交互式控制台使用方面有很大帮助:


但总的来说,我认为这一行额外的代码比单独使用decorator更为清晰。再过3个月,当你阅读自己的代码时,你会感谢你自己。

Decorator只不过是语法中的一种糖类,比如
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。让我知道,如果这是有效的,我没有测试它,但理论上它应该


这里有什么神奇之处:首先,我们需要检索装饰器的参数。这样,我们就可以将它们传递到嵌套函数中。然后,我们接受函数作为参数,最后,我们得到函数的参数。我们可以根据需要嵌套def-s。

这只是简单的函数组合,其中
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))

我认为不一样,在你的帖子中提到,
@customdecorator
正在接收decorator。不会是这种情况。你可以很容易地扩展以添加参数。你可以改为标记为重复,添加扩展,因为我无法从这个答案中实现。我不认为我们应该有两个单独的Q&A来合并带参数和不带参数的装饰器,因此,我将在另一个问题中发布一个更完整的答案。我知道这一点肯定会对我有所帮助。但是(IMHO)既然这个问题已经有了一个有效且被接受的答案,那么很难找到新的(也是正确的)答案。不过你应该使用。如果您尝试打印(myfunc),您将得到
作为输出。当然,您是对的。我有点怀疑,因为这个问题表明我对装饰师缺乏经验,我不确定是否要添加它。我将对它进行编辑,因为最好是第一次正确地学习它。虽然我通常认为保存一行或两行不值得在可读性上损失,但如果我必须对多个函数应用相同的两个装饰符,我肯定会怀疑这是否是偶然的,在最后一个例子中,重构整个过程以避免重复。如果它们来自两个不同的库,这不是偶然的@阿兰菲:哎呀。这将教会我从试图编辑变为快速。@Jacco但我开始想为什么我从来没有在同一个房间里同时看到库的作者。你能详细说明一下吗?是的,我相信这是保存foo()和mycustomdecorator()参数的方法。我错了吗?函数的数量很好
decorator1
decorator2
只是在错误的位置被调用。在定义
wrapper
之前,您希望在
innerdecorator
中应用它们,这样您就不会在每次调用
func
时重复调用它们(我应该说“低效”而不是“错误”。(错误的部分是
wrapper
应该调用而不是返回
func