6

对于我正在编写的装饰器,我想操作函数的特定命名参数。考虑以下装饰器:

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
    return func_caller
return func_decorator

应用于下一个功能:

@square_param('dividend')
def quotient(divisor=1,dividend=0):
    return dividend/divisor

如果将股息作为关键字参数调用,这将起作用,例如:

>>> quotient(dividend=2)
4

但是,当作为位置参数给出时,这将失败。

>>> quotient(3,4)
TypeError: quotient() got multiple values for keyword argument 'dividend'

使用 Python 3,我可以通过强制参数始终作为关键字给出来解决这个问题:

@square_param('dividend')
def quotient(divisor=1,*,dividend=0):
    return dividend/divisor

但我想支持 Python 2,并且我想对函数施加尽可能少的限制。

有没有办法可以在我的装饰器中修复这种行为?

4

3 回答 3

4

首先,您的square_param装饰器不起作用,因为它不返回函数。它需要是:

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
        return func_caller
    return func_decorator

现在我接受了@Dirk 的建议并查看了该inspect模块。您可以通过首先检查参数是否是函数的位置参数之一,然后检查该位置参数是否已指定,然后修改该参数位置来做到这一点。您还需要确保仅在参数作为关键字参数提供时才修改 kwargs。

import inspect

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            funparams = inspect.getargspec(func).args
            if param in funparams:
                i = funparams.index(param)
                if len(args) > i:
                    args = list(args)   # Make mutable
                    args[i] = args[i] * args[i]
            if param in kwargs:
                kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
        return func_caller
    return func_decorator
于 2011-07-05T07:55:05.640 回答
2

即使不使用 Inspect 我们也可以获得函数参数

>>> func = lambda x, y, args: (x, y, {})
>>> func.func_code.co_argcount
3
>>> func.func_code.co_varnames
('x', 'y', 'args')
于 2011-07-05T08:16:52.610 回答
0

这可能只是切线相关,但我发现解决类似问题很有用。我想合并*args**kwargs一个字典中,这样我的以下代码就可以处理而不考虑 args 是如何进入的,而且我不想改变现有kwargs变量,否则我只会使用kwargs.update().

all_args = {**kwargs, **{k: v for k, v in zip(list(inspect.signature(func).parameters), args)}}

# optionally delete `self`
del (all_args['self'])

更新:虽然这有效,但这个答案有更好的技术。部分:

bound_args = inspect.signature(f).bind(*args, **kwargs)
bound_args.apply_defaults()
于 2019-12-04T15:18:05.873 回答