在函数内获取kwargs

36 投票
7 回答
25645 浏览
提问于 2025-04-15 18:10

如果我有一个这样的Python函数:

def some_func(arg1, arg2, arg3=1, arg4=2):

有没有办法在函数内部判断哪些参数是通过关键字传递的呢?

补充说明

对于那些问我为什么需要这个的人,其实我没有特别的理由,只是在一次聊天中提到了这个问题,出于好奇我想知道答案。

7 个回答

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 的风格,但它们展示了可能性。


详细说明

  1. locals() 的结果依赖于函数调用。结果应该是默认值,但会随着传入的值而变化,比如 f(0)f(0, 2, 3) 的结果就不同。
  2. "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'}

否则,可以将一些提到的技术与 FullArgSpecargsdefaults 属性结合使用:

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}

其中,来自普通函数 fFullArgSpec 将显示:

spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a', 'b', 'c'], varargs=None, varkw=None, 
#             defaults=(1, '1'), kwonlyargs=[], 
#             kwonlydefaults=None, annotations={})
13

这是我通过装饰器实现的解决方案:

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"

免责声明:你可能需要考虑一下,是否真的在意参数是怎么传递的。其实这种方法可能并不是必要的。

25

不,按照这个函数的写法,Python代码里没有办法做到这一点。如果你需要这些信息,就得改一下这个函数的写法。

如果你看看Python的C接口,你会发现,普通Python函数的参数传递方式其实是通过一个元组和一个字典来实现的。也就是说,这种方式直接对应于*args, **kwargs的写法。这个元组和字典会被解析成具体的位置参数和命名参数,尽管它们是通过名字传递的。而*a**kw,如果有的话,只会接收那些解析过程中多出来的参数。到这个时候,你的Python代码才会接管控制,但你想要的信息(比如各种参数是怎么传递的)已经不在了。

因此,要获取你想要的信息,就得把函数的写法改成*a, **kw,然后自己进行解析和验证。这就像是“从蛋到煎蛋”,需要做一些工作,但肯定是可行的。而你想要的那种信息,就像是“从煎蛋回到蛋”,根本不可能做到;-)

撰写回答