如果我有一个像这样的python函数:
def some_func(arg1, arg2, arg3=1, arg4=2):
有没有办法确定函数内部通过关键字传递了哪些参数?
编辑
对于那些问我为什么需要这个的人,我没有真正的理由,它是在一次谈话中出现的,我的好奇心占了上风。
如果我有一个像这样的python函数:
def some_func(arg1, arg2, arg3=1, arg4=2):
有没有办法确定函数内部通过关键字传递了哪些参数?
编辑
对于那些问我为什么需要这个的人,我没有真正的理由,它是在一次谈话中出现的,我的好奇心占了上风。
不,没有办法在 Python 代码中使用此签名来执行此操作——如果您需要此信息,则需要更改函数的签名。
如果您查看 Python C API,您会发现将参数传递给普通 Python 函数的实际方式始终是元组加上 dict——即直接反映*args, **kwargs
. 然后将该元组和字典解析为特定的位置参数和在签名中命名的参数,即使它们是按名称传递的,并且*a
and **kw
(如果存在)仅从该解析中获取“溢出”(如果有)-仅在此时您的 Python 代码是否获得控制权,到那时您请求的信息(各种 args 是如何传递的)不再存在。
因此,要获取您请求的信息,请将签名更改为*a, **kw
并进行您自己的解析/验证——这是“从鸡蛋到煎蛋卷”,即一定量的工作,但肯定是可行的,而您正在寻找因为会“从煎蛋卷回到鸡蛋”……根本不可行;-)。
这是我通过装饰器的解决方案:
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"
免责声明:您可能应该考虑您是否真的关心参数是如何传递的。这整个方法可能是不必要的。
有没有办法确定函数内部通过关键字传递了哪些参数?
在尝试评估关键字参数的默认值时,是的,有以下选项:
代码
选项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}
注意:这些选项都不是特别 Pythonic,但它们展示了潜力。
细节
locals()
取决于函数调用。结果应该是默认值,但它们会随着传递给调用的值而变化,例如f(0)
vs.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'}
否则,将一些提到的技术与 的args
和defaults
属性结合起来FullArgSpec
:
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}
FullArgSpec
来自常规函数的将f
显示:
spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a', 'b', 'c'], varargs=None, varkw=None,
# defaults=(1, '1'), kwonlyargs=[],
# kwonlydefaults=None, annotations={})
您几乎将不得不重新定义您的功能:
def some_func(*args, **kwargs):
并自己进行编组。没有办法区分按位置传递、按关键字传递和默认值之间的区别。
你想知道arg3
是1
因为它是从外部传递的还是因为它是默认值?不,据我所知,没有办法做到这一点。我怀疑主要原因是不需要这些知识。通常要做的事情如下:
>>> def func(a, b=None):
if b is None:
# here we know that function was called as:
# func('spam') or func('spam', None) or func('spam', b=None) or func(a='spam', b=None)
b = 42
这样做:
def some_func ( arg1, arg2, arg3=None, arg4=None ):
if arg3 is None:
arg3 = 1 # default value
if arg4 is None:
arg4 = 2 # default value
# do something
这样您就可以看到设置的时间,并且您还可以使用更复杂的默认结构(如列表)而不会遇到以下问题:
>>> def test( arg=[] ):
arg.append( 1 )
print( arg )
>>> test()
[1]
>>> test()
[1, 1]
inspect.signature
hacks的装饰器@awesomo 解决方案的一个变体,它引入了一个“私有”_kwargs
参数,也适用于默认 kwargs:
import inspect
def with_kwargs(func):
sig = inspect.signature(func)
def inner(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
_kwargs = bound.arguments
del _kwargs["_kwargs"]
return func(*args, **kwargs, _kwargs=_kwargs)
return inner
用法:
@with_kwargs
def some_func(a, b, c=3, d=None, _kwargs=None):
print(_kwargs)
>>> some_func(1, b=2, d=4)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}