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

时间:2018-09-26 12:00:26

标签: 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 myfunc(funcpar1, funcpar2):
    ...

由于每次都会发生,因此我想创建一个处理它们的自定义装饰器。像这样:

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

我该如何实现?

3 个答案:

答案 0 :(得分:12)

我认为您不应该-使用装饰器的原始名称可提供更好的可读性。

但是,如果您确实愿意,可以这样做:

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

尽管如此,总的来说,我要说的是,额外多一行代码比单独使用装饰器更为清晰。再过三个月阅读自己的代码,您将感激不尽。

答案 1 :(得分:4)

这只是简单的函数组合,其中decorator1decorator2返回要组合的函数。可以将实际工作抽象为功能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))

答案 2 :(得分:1)

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。