在函数内获取kwargs
如果我有一个这样的Python函数:
def some_func(arg1, arg2, arg3=1, arg4=2):
有没有办法在函数内部判断哪些参数是通过关键字传递的呢?
补充说明
对于那些问我为什么需要这个的人,其实我没有特别的理由,只是在一次聊天中提到了这个问题,出于好奇我想知道答案。
7 个回答
有没有办法在函数内部判断哪些参数是通过关键字传递的呢?
在尝试评估关键字参数的默认值时,确实有一些方法可以做到:
代码
选项 1 - locals()
def f(a, b=1, c="1"):
print(locals())
f(0)
# {'c': '1', 'b': 1, 'a': 0}
选项 2 - 部分类型提示*
def g(a, b:int=1, c:str="1"):
pass
keys = g.__annotations__
values = g.__defaults__
dict(zip(keys, values))
# {'b': 1, 'c': '1'}
选项 3 - 完整类型提示*
def h(a:float, b:int=1, c:str="1") -> int:
return 0
keys = reversed(list(filter(lambda x: x != "return", h.__annotations__)))
values = reversed(h.__defaults__)
{k: v for k, v in zip(keys, values) if k != "return"}
# {'c': '1', 'b': 1}
注意:这些方法都不是特别符合 Python 的风格,但它们展示了可能性。
详细说明
locals()
的结果依赖于函数调用。结果应该是默认值,但会随着传入的值而变化,比如f(0)
和f(0, 2, 3)
的结果就不同。- "return" 的标注是可选的,我们会把它从我们的键中过滤掉。此外,我们会反向遍历以去掉可能的按位置传递的参数,使用
zip()
来处理。
*这些选项依赖于类型提示和关键字插入顺序的保持(Python 3.6 及以上版本)。它们只提供默认值,并不会随着函数调用的值而改变。目前在 Python 中,类型提示是可选的,因此在生产代码中使用时要谨慎。
建议
我建议只在调试或快速检查函数签名时使用后面提到的方法。实际上,考虑到仅限关键字的参数(在 *
右侧),可以使用 inspect.getargspec()
来捕获 kwonlydefaults
字典。
def i(a, *, b=1, c="1"):
pass
spec = inspect.getfullargspec(i)
spec
# FullArgSpec(args=['a'], varargs=None, varkw=None,
# defaults=None, kwonlyargs=['b', 'c'],
# kwonlydefaults={'b': 1, 'c': '1'}, annotations={})
spec.kwonlydefaults
# {'b': 1, 'c': '1'}
否则,可以将一些提到的技术与 FullArgSpec
的 args
和 defaults
属性结合使用:
def get_keywords(func):
"""Return a dict of (reversed) keyword arguments from a function."""
spec = inspect.getfullargspec(func)
keys = reversed(spec.args)
values = reversed(spec.defaults)
return {k: v for k, v in zip(keys, values)}
get_keywords(f)
# {'c': '1', 'b': 1}
其中,来自普通函数 f
的 FullArgSpec
将显示:
spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a', 'b', 'c'], varargs=None, varkw=None,
# defaults=(1, '1'), kwonlyargs=[],
# kwonlydefaults=None, annotations={})
这是我通过装饰器实现的解决方案:
def showargs(function):
def inner(*args, **kwargs):
return function((args, kwargs), *args, **kwargs)
return inner
@showargs
def some_func(info, arg1, arg2, arg3=1, arg4=2):
print arg1,arg2,arg3,arg4
return info
In [226]: some_func(1,2,3, arg4=4)
1 2 3 4
Out[226]: ((1, 2, 3), {'arg4': 4})
可能还有其他方法可以进一步简化这个,但对我来说,这种方式对原有代码的影响最小,而且不需要修改调用代码。
编辑:如果你想测试某些参数是否是通过关键字传递的,可以在some_func里面做类似下面的事情:
args, kwargs = info
if 'arg4' in kwargs:
print "arg4 passed as keyword argument"
免责声明:你可能需要考虑一下,是否真的在意参数是怎么传递的。其实这种方法可能并不是必要的。
不,按照这个函数的写法,Python代码里没有办法做到这一点。如果你需要这些信息,就得改一下这个函数的写法。
如果你看看Python的C接口,你会发现,普通Python函数的参数传递方式其实是通过一个元组和一个字典来实现的。也就是说,这种方式直接对应于*args, **kwargs
的写法。这个元组和字典会被解析成具体的位置参数和命名参数,尽管它们是通过名字传递的。而*a
和**kw
,如果有的话,只会接收那些解析过程中多出来的参数。到这个时候,你的Python代码才会接管控制,但你想要的信息(比如各种参数是怎么传递的)已经不在了。
因此,要获取你想要的信息,就得把函数的写法改成*a, **kw
,然后自己进行解析和验证。这就像是“从蛋到煎蛋”,需要做一些工作,但肯定是可行的。而你想要的那种信息,就像是“从煎蛋回到蛋”,根本不可能做到;-)