将带参数的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
)