为什么解包非标识符字符串可以在函数调用中起作用?

tho*_*len 7 python python-3.x

令我惊讶的是,我注意到在函数调用中,我可以用甚至不是有效的 python 标识符的dict字符串来解压 a 。

让我感到惊讶的是,因为参数名称必须是标识符,因此允许函数调用解包**kwargs具有非标识符的 a 且没有运行时错误,这似乎并不健康(因为它可能会将问题埋藏得比实际发生的地方更深)。

除非能够做到这一点有实际用途,在这种情况下我的问题就变成“那会有什么用途?”。

示例代码

考虑这个函数:

def foo(**kwargs):
    first_key, first_val = next(iter(kwargs.items()))
    print(f"{first_key=}, {first_val=}")
    return kwargs
Run Code Online (Sandbox Code Playgroud)

这表明,在函数调用中,您无法解压dict具有整数键的 a,这是预期的。

>>> t = foo(**{1: 2, 3: 4})
TypeError                                 Traceback (most recent call last)
...
TypeError: foo() keywords must be strings
Run Code Online (Sandbox Code Playgroud)

另一方面,真正出乎意料且令人惊讶的是,您可以使用dict字符串键解压 a,即使这些不是有效的 python 标识符:

>>> t = foo(**{'not an identifier': 1, '12': 12, ',(*&$)': 100})
first_key='not an identifier', first_val=1
>>> t
{'not an identifier': 1, '12': 12, ',(*&$)': 100}
Run Code Online (Sandbox Code Playgroud)

d.b*_*d.b 1

看起来这更像是一个kwargs问题而不是拆包问题。例如,人们不会遇到同样的问题foo

def foo(a, b):
    print(a + b)

foo(**{"a": 3, "b": 2})
# 5

foo(**{"a": 3, "b": 2, "c": 4})
# TypeError: foo() got an unexpected keyword argument 'c'

foo(**{"a": 3, "b": 2, "not valid": 4})
# TypeError: foo() got an unexpected keyword argument 'not valid'
Run Code Online (Sandbox Code Playgroud)

kwargs使用时,这种灵活性是有代价的。看起来该函数首先尝试弹出并映射所有命名参数,然后将剩余项传递到被调用dictkwargs. 由于所有关键字都是字符串(但所有字符串都不是有效关键字),因此第一个检查很容易 - keywords must be strings。除此之外,由作者决定如何处理 中的剩余项目kwargs

def bar(a, **kwargs):
    print(locals())
    
bar(a=2)
# {'a': 2, 'kwargs': {}}

bar(**{"a": 3, "b": 2})
# {'a': 3, 'kwargs': {'b': 2}}

bar(**{"a": 3, "b": 2, "c": 4})
# {'a': 3, 'kwargs': {'b': 2, 'c': 4}}

bar(**{1: 3, 3: 4})
# TypeError: keywords must be strings
Run Code Online (Sandbox Code Playgroud)

综上所述,确实存在不一致之处,但不是缺陷。一些相关讨论:

  1. 支持(或不支持)**kwargs 中的无效标识符
  2. 功能:**kwargs 允许不正确命名的变量