Python将args转换为kwargs
我正在写一个装饰器,这个装饰器需要在调用被装饰的函数之前,先调用其他一些函数。被装饰的函数可能会有位置参数,但装饰器要调用的那些函数只能接受关键字参数。有没有简单的方法可以把位置参数转换成关键字参数呢?
我知道我可以获取被装饰函数的变量名列表:
>>> 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 个回答
如果你使用的是 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()
更好用。
任何以位置方式传递的参数都会被放到 *args 里。而任何以关键字方式传递的参数则会被放到 **kwargs 里。
如果你有位置参数的值和名称,那么你可以这样做:
kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))
这样可以把它们全部转换成关键字参数。
注意 - 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。