33

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

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

有没有办法确定函数内部通过关键字传递了哪些参数?

编辑

对于那些问我为什么需要这个的人,我没有真正的理由,它是在一次谈话中出现的,我的好奇心占了上风。

4

7 回答 7

22

不,没有办法在 Python 代码中使用此签名来执行此操作——如果您需要此信息,则需要更改函数的签名。

如果您查看 Python C API,您会发现将参数传递给普通 Python 函数的实际方式始终是元组加上 dict——即直接反映*args, **kwargs. 然后将该元组和字典解析为特定的位置参数和在签名中命名的参数,即使它们是按名称传递的,并且*aand **kw(如果存在)仅从该解析中获取“溢出”(如果有)-仅在此时您的 Python 代码是否获得控制权,到那时您请求的信息(各种 args 是如何传递的)不再存在。

因此,要获取您请求的信息,请将签名更改为*a, **kw并进行您自己的解析/验证——这是“从鸡蛋到煎蛋卷”,即一定量的工作,但肯定是可行的,而您正在寻找因为会“从煎蛋卷回到鸡蛋”……根本不可行;-)。

于 2010-01-18T18:00:04.273 回答
12

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

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"

免责声明:您可能应该考虑您是否真的关心参数是如何传递的。这整个方法可能是不必要的。

于 2010-01-18T18:05:59.300 回答
4

有没有办法确定函数内部通过关键字传递了哪些参数?

在尝试评估关键字参数的默认值时,是的,有以下选项:

代码

选项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,但它们展示了潜力。


细节

  1. locals()取决于函数调用。结果应该是默认值,但它们会随着传递给调用的值而变化,例如f(0)vs.f(0 2, 3)
  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'}

否则,将一些提到的技术与 的argsdefaults属性结合起来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={})
于 2019-03-08T22:25:52.660 回答
3

您几乎将不得不重新定义您的功能:

def some_func(*args, **kwargs):

并自己进行编组。没有办法区分按位置传递、按关键字传递和默认值之间的区别。

于 2010-01-18T18:01:37.060 回答
1

你想知道arg31因为它是从外部传递的还是因为它是默认值?不,据我所知,没有办法做到这一点。我怀疑主要原因是不需要这些知识。通常要做的事情如下:

>>> 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
于 2010-01-18T17:55:56.957 回答
1

这样做:

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]
于 2010-01-18T18:01:07.797 回答
0

_kwargs 带有inspect.signaturehacks的装饰器

@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}
于 2022-02-04T14:52:55.290 回答