在Python中对参数操作的嵌套函数装饰器

11 投票
1 回答
5077 浏览
提问于 2025-04-17 03:59

我正在写一个函数装饰器,这个装饰器会对函数的第一个参数进行转换。如果我只给函数加一次装饰器,效果很好,但如果我给它加两次装饰器,就会出错。下面是一些代码,展示了这个问题,这段代码是我正在处理的代码的简化版本。我省略了进行转换的代码,以免让问题变得复杂。

from inspect import getargspec
from functools import wraps

def dec(id):
    def _dec(fn):
        @wraps(fn)
        def __dec(*args, **kwargs):
            if len(args):
                return fn(args[0], *args[1:], **kwargs)
            else:
                first_arg = getargspec(fn).args[0]
                new_kwargs = kwargs.copy()
                del new_kwargs[first_arg]
                return fn(kwargs[first_arg], **new_kwargs)
        return __dec
    return _dec

@dec(1)
def functionWithOneDecorator(a, b, c):
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)

@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)

functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)

functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)

当我运行上面的代码时,得到的输出是:

functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
IndexError: list index out of range

出现这个问题是因为,当第二个装饰器检查它要装饰的函数时,它试图找到参数名称,但失败了,因为它正在装饰一个装饰器,而装饰器只接受 *args 和 **kwargs 这两种参数。

我能想到一些解决这个问题的方法,这些方法在上面的代码中有效,但如果一个函数同时被我的装饰器和其他第三方的装饰器装饰,就会出问题。有没有一种通用的方法来解决这个问题?或者有没有更好的方法来实现同样的效果?

更新:感谢 @Hernan 指出 decorator 模块。这个模块正好解决了这个问题。现在我的代码看起来是这样的:

from decorator import decorator

def dec(id):
    @decorator
    def _dec(fn, *args, **kwargs):
        return fn(args[0], *args[1:], **kwargs)
    return _dec

@dec(1)
def functionWithOneDecorator(a, b, c):
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)

@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)

functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)

functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)    

看起来更简洁了,并且 它可以正常工作!

1 个回答

5

问题在于,你装饰过的函数的参数和原始函数的参数不一致。这个问题在装饰器模块的帮助文档中解释得很清楚,可以帮助你解决这个问题。简单来说,你应该使用保持签名的装饰器,这样第二个装饰器就能看到和第一个装饰器一样的参数。

撰写回答