在Python中,使用单个(不是双个)星号*解包字典时,它代表什么意思?

7

有人能解释一下使用单个或双个星号来解包字典的区别吗?如果在函数参数中使用时有所不同,您可以提到它们的差异,但我认为这并不相关。

然而,它们共享相同的星号语法,因此可能存在某些相关性。

def foo(a,b)
    return a+b

tmp = {1:2,3:4}
foo(*tmp)        #you get 4
foo(**tmp)       #typeError: keyword should be string. Why it bothers to check the type of keyword? 

此外,在这种情况下,为什么字典的键不允许作为函数参数时非字符串?是否有任何例外?他们为什么设计 Python 是这样的,是因为编译器无法在这里推断类型或其他原因吗?

1
单个星号解包获取键,因此它添加了1和3。使用双个星号时,它试图调用foo(1=2, 3=4),这没有任何意义。关键字参数必须是有效的标识符 - alkasm
回应“为什么要检查关键字的类型?”函数的命名参数只能是字符串,因此尝试使用非字符串名称保证名称无法匹配。 CPython利用这一点,使用专门的查找函数来查找仅由字符串组成的dict(许多实现内部恰好如此),因此拒绝非字符串可以确保字符串可以通过最快的代码路径(加速所有Python代码)。 - ShadowRanger
5个回答

8
当字典作为列表进行迭代时,迭代会获取其键,例如:
for key in tmp:
    print(key)

意思相同。
for key in tmp.keys():
    print(key)

在这种情况下,将*tmp解包相当于使用*tmp.keys(),忽略其值。如果想要使用这些值,可以使用*tmp.values()
双星号用于定义带有关键字参数的函数,例如:
def foo(a, b):

或者

def foo(**kwargs):

在这里,您可以将参数存储在字典中,并将其作为**tmp传递。在第一种情况下,键必须是函数定义中参数名称的字符串。而在第二种情况下,您可以在函数内部将kwargs视为字典进行操作。


2
可能将链接到表达式的文档会有帮助,其中这两者都得到了正式解释。("如果在函数调用中出现语法 *expression,则 expression 必须计算为可迭代对象。") - Brad Solomon
2
回复还不错,但我想挑战一下,让你的答案更好(尤其是因为这个主题实际上非常复杂)。您可以在不设置默认值的情况下要求关键字参数,称为仅限关键字参数。此外,使用**kwargs收集关键字参数与具有kw-only args或具有默认值的kwargs略有不同---特别是,您可以发送不是有效Python标识符的字符串。 - alkasm
1
“双星号用于定义带有关键字参数(默认值)的函数”这种说法并不准确。如果我这样写:def add(a,b): return a+b,然后再定义一个字典 tmp = {'a':1,'b':2},最后调用 add(**tmp),仍然可以得到3这个结果。所以函数参数并不需要默认值? - Han XIAO
虽然你的回答很好,从技术上讲,你没有解释他们为什么会得到他们得到的错误。具体来说,int被用作关键字。如果他们的字典使用字符串键,它就会给出不同的错误信息。 - Jab

3
def foo(a,b)
   return a+b

tmp = {1:2,3:4}
foo(*tmp)        #you get 4
foo(**tmp) 

在这种情况下:
foo(*tmp) 表示 foo(1, 3)
foo(**tmp) 表示 foo(1=2, 3=4),但会因为 1 不是一个参数而导致错误。参数必须是字符串,并且(感谢 @Alexander Reynolds 指出)必须以下划线或字母开头。参数必须是一个有效的 Python 标识符。这意味着你甚至不能做像这样的事情:
def foo(1=2, 3=4):
   <your code>

或者

def foo('1'=2, '3'=4):
   <your code>

请查看python基本语法获取更多信息。

1
你可以通过链接文档来改进你的回答;目前来看它并不完全正确(例如,'2'是一个字符串但不是有效的Python标识符)。 - alkasm

1
这是一个扩展可迭代解包
>>> def add(a=0, b=0):
...     return a + b
...
>>> d = {'a': 2, 'b': 3}
>>> add(**d)#corresponding to add(a=2,b=3)
5

对于单个星号,

def add(a=0, b=0):
    ...     return a + b
    ...
    >>> d = {'a': 2, 'b': 3}
    >>> add(*d)#corresponding to add(a='a',b='b')
    ab

了解更多这里


扩展的可迭代对象解包与 *args,**kwargs 解包是不同的。 - alkasm

0

我认为函数参数中的双星号和解包字典直观地表示为:

#suppose you have this function
def foo(a,**b):
    print(a)
    for x in b:
        print(x,"...",b[x])
#suppose you call this function in the following form
foo(whatever,m=1,n=2)   
#the m=1 syntax actually means assign parameter by name, like foo(a = whatever, m = 1, n = 2)
#so you can also do foo(whatever,**{"m":1,"n":2})
#the reason for this syntax is you actually do
**b is m=1,n=2 #something like pattern matching mechanism
so b is {"m":1,"n":2}, note "m" and "n" are now in string form
#the function is actually this:
def foo(a,**b):  # b = {"m":1,"n":2}
    print(a)
    for x in b:  #for x in b.keys(), thanks to @vlizana answer
        print(x,"...",b[x])

所有的语法现在都很清晰了。对于单个星号也是一样的。值得注意的是,如果您使用单个星号来解包字典,实际上您正在尝试以列表方式解包它,并且只有字典的键被解包。

0

[https://docs.python.org/3/reference/expressions.html#calls]

这意味着尽管*表达式语法可能出现在显式关键字参数之后,但它会在关键字参数(和任何**表达式参数-见下文)之前进行处理。所以:
def f(a, b):
print(a, b)

f(b=1, *(2,))
f(a=1, *(2,))
#Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
#TypeError: f() got multiple values for keyword argument 'a'
f(1, *(2,))

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接