获取函数内的 kwargs

发布于 2024-08-18 10:17:25 字数 212 浏览 8 评论 0原文

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

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

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

编辑

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

If I have a python function like so:

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

Is there a way to determine which arguments were passed by keyword from inside the function?

EDIT

For those asking why I need this, I have no real reason, it came up in a conversation and curiosity got the better of me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

帅气尐潴 2024-08-25 10:17:25

不,无法在具有此签名的 Python 代码中执行此操作 - 如果您需要此信息,则需要更改函数的签名。

如果您查看 Python C API,您会发现参数传递给普通 Python 函数的实际方式始终是元组加字典的形式——即,直接反映 签名的方式*args,**kwargs。然后,该元组和字典被解析为特定的位置参数和在签名中命名的参数,即使它们是按名称传递的,以及 *a**kw,如果存在,则仅从该解析中获取“溢出”(如果有)——只有此时您的 Python 代码才能获得控制权,然后您所请求的信息(如何是各种参数通过)已经不存在了。

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

No, there is no way to do it in Python code with this signature -- if you need this information, you need to change the function's signature.

If you look at the Python C API, you'll see that the actual way arguments are passed to a normal Python function is always as a tuple plus a dict -- i.e., the way that's a direct reflection of a signature of *args, **kwargs. That tuple and dict are then parsed into specific positional args and ones that are named in the signature even though they were passed by name, and the *a and **kw, if present, only take the "overflow" from that parsing, if any -- only at this point does your Python code get control, and by then the information you're requesting (how were the various args passed) is not around any more.

To get the information you requested, therefore, change the signature to *a, **kw and do your own parsing/validation -- this is going "from the egg to the omelette", i.e. a certain amount of work but certainly feasible, while what you're looking for would be going "from the omelette back to the egg"... simply not feasible;-).

星軌x 2024-08-25 10:17:25

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

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"

免责声明:您可能应该考虑是否真正关心如何传递论点获得通过。整个方法可能是不必要的。

Here's my solution via decorators:

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})

There may be a way to clean this up further, but this seems minimally intrusive to me and requires no change to the calling code.

Edit: To actually test if particular args were passed by keyword, then do something like the following inside of some_func:

args, kwargs = info
if 'arg4' in kwargs:
    print "arg4 passed as keyword argument"

Disclaimer: you should probably consider whether or not you really care how the arguments were passed. This whole approach may be unnecessary.

久夏青 2024-08-25 10:17:25

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

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

代码

选项 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)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'}

否则,将提到的一些技术与 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}

其中 FullArgSpec 来自常规函数 f 将显示:

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

Is there a way to determine which arguments were passed by keyword from inside the function?

In trying to assess default values of keyword parameters, yes there are options:

Code

Option 1 - locals()

def f(a, b=1, c="1"):
    print(locals())


f(0)
# {'c': '1', 'b': 1, 'a': 0}

Option 2 - Partial Type Hints*

def g(a, b:int=1, c:str="1"):
    pass


keys = g.__annotations__
values = g.__defaults__

dict(zip(keys, values))
# {'b': 1, 'c': '1'}

Option 3 - Full Type Hints*

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}

Note: None of these options are particularly Pythonic, but they demonstrate potential.


Details

  1. locals() depends on the function call. The results should be default values, but they change with values passed into the call, e.g. f(0) vs. f(0 2, 3)
  2. "Partial" type hints mean only keyword parameters are annotated. Adding any other annotations will not work with this naive approach.
  3. "Full" or complete type hints may include other parameters. Since a "return" annotation is optional, we filter it from our keys. Furthermore, we iterate backwards to trim potential positional parameters with zip().

*These options depend on type hints and key insertion order preservation (Python 3.6+). They only give the default values and do not change with function call values. Type hints are optional right now in Python, and thus should be used with caution in production code.


Suggestion

I would only use the latter approaches to debug or quickly inspect a function's signature. In fact, given keyword-only arguments (right of the *), one can use inspect.getargspec() to capture the kwonlydefaults dict.

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'}

Otherwise, combine some of the mentioned techniques with the args and defaults attributes of 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}

where the FullArgSpec from the regular function f would show:

spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a', 'b', 'c'], varargs=None, varkw=None, 
#             defaults=(1, '1'), kwonlyargs=[], 
#             kwonlydefaults=None, annotations={})
凉宸 2024-08-25 10:17:25

您几乎必须重新定义您的功能:

def some_func(*args, **kwargs):

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

You're pretty much going to have to redefine your function:

def some_func(*args, **kwargs):

and do the marshaling yourself. There's no way to tell the difference between pass-by-position, pass-by-keyword, and default.

你怎么敢 2024-08-25 10:17:25

只需这样做:

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]

Just do it like this:

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

That way you can see when something was set, and you are also able to work with more complex default structures (like lists) without running into problems like these:

>>> def test( arg=[] ):
        arg.append( 1 )
        print( arg )
>>> test()
[1]
>>> test()
[1, 1]
心的憧憬 2024-08-25 10:17:25

您想知道 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

do you want to know whether arg3 was 1 because it was passed from outside or because it was a default value? No there is no way to do this as far as I'm aware. The main reason, I suspect, that there is no need for such knowledge. What typically is done is the following:

>>> 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
流年里的时光 2024-08-25 10:17:25

带有 inspect.signature 的 _kwargs 装饰器 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}

_kwargs decorator with inspect.signature hacks

A variant of @awesomo's solution which introduces a "private" _kwargs parameter and also works with default 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

Usage:

@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}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文