在函数调用中,**(双星号/星号)和*(星号/星号)代表什么意思?

790

补充:https://dev59.com/eXM_5IYBdhLWcg3w-4hg - wds
4
我认为这应该被表述为“*函数调用语法”。虽然它们不是运算符,但会让人困惑,因为存在与此语法无关的 *** 运算符。 - Ian Bicking
1
@Ian Bicking:你是完全正确的,参数列表中的 * 和 ** 是纯语法(符号)。 - P. Ortiz
3
注意:针对 PEP 448:其他解包概括 中的特定内容(例如 [*a, b, *c]{**d1, **d2}),您需要阅读 元组、列表和集合定义中的星号,字典定义中的双星号,该链接专门介绍了在函数调用和函数定义之外使用星号的情况。而关于早期的 PEP 3132,请参见 当您不知道序列长度时的 Python 多重解包赋值 - ShadowRanger
4个回答

1178

一个单独的星号*将序列或集合解包为定位参数。假设我们有

def add(a, b):
    return a + b

values = (1, 2)

使用*拆封运算符,我们可以编写s = add(*values),这将等同于编写s = add(1, 2)

双星号**对于字典也执行相同的操作,为命名参数提供值:

values = { 'a': 1, 'b': 2 }
s = add(**values) # equivalent to add(a=1, b=2)

这两个运算符可以用于相同的函数调用。例如,给定:

def sum(a, b, c, d):
    return a + b + c + d

values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }

如果执行 s = add(*values1, **values2),则等价于 s = sum(1, 2, c=10, d=15)

在Python文档的教程中,相关章节也有介绍。


类似地,***也可以用于参数。使用*允许函数接受任意数量的位置参数,并将它们收集到单个参数中:

def add(*values):
    s = 0
    for v in values:
        s = s + v
    return s

现在当函数被调用时,例如s = add(1, 2, 3, 4, 5)values将会是元组(1, 2, 3, 4, 5)(这显然会产生结果15)。

同样的,如果一个参数标记为**,它将接收到一个dict

def get_a(**values):
    return values['a']

s = get_a(a=1, b=2)      # returns 1

这允许在不必声明它们的情况下指定大量可选参数。

同样,两者可以结合使用:

def add(*values, **options):
    s = 0
    for i in values:
        s = s + i
    if "neg" in options:
        if options["neg"]:
            s = -s
    return s
        
s = add(1, 2, 3, 4, 5)            # returns 15
s = add(1, 2, 3, 4, 5, neg=True)  # returns -15
s = add(1, 2, 3, 4, 5, neg=False) # returns 15

10
为什么需要这个,函数不可以在不扩展列表的情况下迭代提供的列表吗? - Martin Beckett
38
好的,但是你需要这样调用它:s = sum((1, 2, 3, 4, 5))或者s = sum([1, 2, 3, 4, 5])。使用*values选项可以让函数调用看起来像接受了多个参数,但是实际上这些参数被打包成一个集合传递给函数代码。 - Lasse V. Karlsen
15
这是真正的好处:如果您需要使用可变数量的参数编写函数,Python可以使其成为可能。例如,C语言中的printf函数具有1+n个参数,对于任何初学者来说,编写此函数可能比较棘手。但在Python中,初学者可以轻松编写 def printf(string_template, *args) 并继续进行其他任务。 - IceArdor
2
如果您(可能是无意中:p)使用一个而不是两个解包只有一个字典会发生什么?它似乎会做一些事情,看起来像一个元组出现了,但它并不是那么明显。 (编辑:好吧,我认为答案是它只是解包键,值被丢弃了) - Ben Farmer
2
最后一个例子意味着 * 和 ** 不仅可以解包,还可以打包!请参阅此优秀页面 https://www.codingame.com/playgrounds/500/advanced-python-features#unpacking。 - H.C.Chen
显示剩余2条评论

29
在函数调用中,单个星号将列表转换为单独的参数(例如,zip(* x)相当于给定x = [x1,x2,x3]时的zip(x1,x2,x3)),双个星号将字典转换为单独的关键字参数(例如,给定k = {'x':my_x,'y':my_y}时,f(**k)相当于f(x=my_x,y=my_y))。
在函数定义中,情况正好相反:单个星号将任意数量的参数转换为列表,而双个星号将任意数量的关键字参数转换为字典。例如,def foo(*x)表示“foo接受任意数量的参数,并且它们可以通过x进行访问(即如果用户调用foo(1,2,3),则x将是(1, 2, 3))”,而def bar(**k)表示“bar接受任意数量的关键字参数,并且它们可以通过k进行访问(即如果用户调用bar(x=42,y=23),则k将是{'x':42,'y':23})”。

24

我发现这对于存储函数调用的参数非常有用。

例如,假设我有一些针对函数“add”的单元测试:

def add(a, b):
    return a + b

tests = { (1,4):5, (0, 0):0, (-1, 3):3 }

for test, result in tests.items():
    print('test: adding', test, '==', result, '---', add(*test) == result)

除了手动进行类似add(test[0], test[1])的操作之外,没有其他调用add的方式,这样写很不优雅。而且,如果变量数量是可变的,那么代码将变得非常难看,需要大量的if语句。

在定义工厂对象(创建其他对象的对象)时,这种方法也很有用。假设你有一个名为Factory的类,它制造汽车对象并返回它们。你可以使myFactory.make_car('red', 'bmw', '335ix')创建Car('red', 'bmw', '335ix'),并返回它。

def make_car(*args):
    return Car(*args)

当你想调用超类的构造函数时,这也非常有用。


6
我喜欢你的例子。但是,我认为-1 + 3 == 2。 - eksortso
8
我故意放了一些会失败的东西:) - Donald Miner

20
它被称为扩展调用语法。根据文档
如果函数调用中出现 *expression 语法,expression必须评估为序列。来自此序列的元素将被视为附加的位置参数;如果存在位置参数x1, ..., xN,且expression评估为序列y1, ..., yM,则相当于具有M+N个位置参数x1, ..., xN, y1, ..., yM的调用。
如果函数调用中出现 **expression 语法,expression必须评估为映射,其中的内容将被视为额外的关键字参数。在表达式和作为显式关键字参数出现的关键字相同时,引发TypeError异常。

3
在教科书答案中添加一个脚注——在语法支持到来之前,使用内置的 apply() 函数实现了相同的功能。 - Jeremy Brown

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