Python将args转换为kwargs

35 投票
7 回答
18274 浏览
提问于 2025-04-15 11:26

我正在写一个装饰器,这个装饰器需要在调用被装饰的函数之前,先调用其他一些函数。被装饰的函数可能会有位置参数,但装饰器要调用的那些函数只能接受关键字参数。有没有简单的方法可以把位置参数转换成关键字参数呢?

我知道我可以获取被装饰函数的变量名列表:

>>> def a(one, two=2):
...    pass

>>> a.func_code.co_varnames
('one', 'two')

但是我搞不清楚哪些是位置参数,哪些是关键字参数。

我的装饰器是这样的:

class mydec(object):
    def __init__(self, f, *args, **kwargs):
        self.f = f

    def __call__(self, *args, **kwargs):
        hozer(**kwargs)
        self.f(*args, **kwargs)

除了比较关键字参数和变量名列表,把不在里面的东西加到关键字参数里,然后希望一切顺利之外,还有其他方法吗?

7 个回答

22

如果你使用的是 Python 版本 2.7 或更高,inspect.getcallargs() 这个功能可以直接帮你完成这个任务。你只需要把被装饰的函数作为第一个参数传进去,然后其他参数就按照你打算调用它的方式传入。举个例子:

>>> def f(p1, p2, k1=None, k2=None, **kwargs):
...     pass
>>> from inspect import getcallargs

我打算这样调用 f('p1', 'p2', 'p3', k2='k2', extra='kx1')(注意 k1 是作为 p3 传入的),所以...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1')
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}}

如果你知道这个被装饰的函数不会使用 **kwargs,那么这个键就不会出现在字典里,这样就完成了(我假设没有 *args,因为那样会打破所有参数都有名字的要求)。如果你确实有 **kwargs,就像我这个例子里那样,并且想把它们和其他命名参数一起包含进去,那就需要多写一行代码:

>>> call_args.update(call_args.pop('kwargs'))
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'}

更新:对于 Python 版本 3.3 及以上,可以查看 inspect.Signature.bind() 和相关的 inspect.signature 函数,它们提供了类似但更强大的功能,比 inspect.getcallargs() 更好用。

25

任何以位置方式传递的参数都会被放到 *args 里。而任何以关键字方式传递的参数则会被放到 **kwargs 里。
如果你有位置参数的值和名称,那么你可以这样做:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))

这样可以把它们全部转换成关键字参数。

9

注意 - co_varnames 会包含局部变量和关键字。这可能没什么大不了的,因为 zip 会截断较短的序列,但如果你传入的参数数量不对,可能会导致一些让人困惑的错误信息。

你可以通过 func_code.co_varnames[:func_code.co_argcount] 来避免这个问题,但更好的方法是使用 inspect 模块。也就是说:

import inspect
argnames, varargs, kwargs, defaults = inspect.getargspec(func)

你可能还想处理函数定义了 **kwargs*args 的情况(即使只是为了在与装饰器一起使用时抛出异常)。如果这些被设置了,getargspec 的第二个和第三个结果会返回它们的变量名,否则它们会是 None。

撰写回答