将带有参数的 Python 装饰器合并为一个 [重复]

Posted

技术标签:

【中文标题】将带有参数的 Python 装饰器合并为一个 [重复]【英文标题】:Merging Python decorators with arguments into a single one [duplicate] 【发布时间】:2019-03-02 04:55:00 【问题描述】:

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

我如何做到这一点?

【问题讨论】:

我认为不一样,在帖子中您提到@customdecorator 正在接收装饰器。不会是这种情况。 您可以轻松扩展this answer 以添加参数。 您能否改为将其标记为重复,添加扩展名,因为我无法从该答案中实现这一点。 我认为我们不应该有两个单独的问答来合并带和不带参数的装饰器,所以我将在另一个问题中发布更完整的答案。 我知道这一点肯定会帮助我。但是(恕我直言)由于该问题已经有一个有效且可接受的答案,因此很难找到新的(并且也是正确的)答案。 【参考方案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):
    ...

不过,基于 cmets,这里有另一种方法:

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

不过,总的来说,我会说多出一行代码非常值得单独使用装饰器的清晰度。再过 3 个月,当您阅读自己的代码时,您会感谢自己。

【讨论】:

你应该使用functools.wraps。如果您尝试print(myfunc),您将得到<function my_decorator.<locals>.inner.<locals>.newfunc at 0x7f5ee7bbee18> 作为输出。 你是对的,当然。我有点怀疑,因为这个问题表明对装饰者有一定的经验,我不确定是否要添加它。我会对其进行编辑,因为最好第一次就学会它。 虽然我通常同意保存一两行不值得损失可读性,但如果我必须将相同的两个装饰器应用于多个函数,我当然想知道这是偶然的还是不是,在最后一种情况下,重构整个事情以避免重复。 如果它们来自两个不同的库,这并非偶然。 @Aran-Fey:哎呀。这将教会我从尝试编辑到快速编辑。 @Jacco 但后来我开始想知道为什么我从来没有在同一个房间里同时看到图书馆的作者。【参考方案2】:

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

【讨论】:

【参考方案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。让我知道这是否有效,我没有测试它,但理论上应该。

这有什么神奇之处:首先,我们需要为我们的装饰器检索参数。这样,我们就可以将它们传递给嵌套函数。然后,我们接受我们的函数作为参数,最后,我们获取函数的参数。我们可以尽可能多地嵌套我们的 def-s。

【讨论】:

您能详细说明一下吗? 是的,我相信这是为 foo() 和 mycustomdecorator() 保留参数的方法。我错了吗? 函数数量还可以; decorator1decorator2 只是在错误的地方被调用。你想在innerdecorator 中应用它们 之前 wrapper 被定义,这样你就不会在每次调用func 时重复调用它们。 (我应该说“低效”而不是“错误”。) (错误的部分是wrapper应该调用,而不是返回,func。)

以上是关于将带有参数的 Python 装饰器合并为一个 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

将命令行参数传递给调用带有装饰器参数的装饰函数的函数

python 通用装饰器,带有参数的装饰器,

基于 Python 类的装饰器,带有可以装饰方法或函数的参数

Python进阶装饰器(Decorator)

python -- 带有参数的装饰器

python之带有参数的装饰器