ChatGPT解决这个技术问题 Extra ChatGPT

函数调用中的星号和双星号运算符是什么意思?

* 运算符在 Python 中是什么意思,例如在 zip(*x)f(**k) 之类的代码中?

它在解释器内部是如何处理的?它会影响性能吗?它是快还是慢?什么时候有用,什么时候没用?它应该用在函数声明还是调用中?

我认为这应该被表述为“*函数调用语法”。它们不是运算符,但会让人感到困惑,因为 is 有一个与此语法无关的 *** 运算符。
@Ian Bicking:您完全正确,参数列表中的 * 和 ** 是纯语法(令牌)。
注意:对于 PEP 448: Additional Unpacking Generalizations 特定的东西(例如 [*a, b, *c]{**d1, **d2}),您需要阅读 asterisk in tuple, list and set definitions, double asterisk in dict definition,它专门针对函数调用和函数定义的 outside 的使用。对于前面的 PEP 3132,请参阅 Multiple Unpacking Assignment in Python when you don't know the sequence length

Z
Zero

单星 * 将序列/集合解压缩为位置参数,因此您可以这样做:

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

values = (1, 2)

s = sum(*values)

这将解包元组,使其实际执行为:

s = sum(1, 2)

双星 ** 做同样的事情,只使用字典并因此命名参数:

values = { 'a': 1, 'b': 2 }
s = sum(**values)

您还可以组合:

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

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

将执行为:

s = sum(1, 2, c=10, d=15)

另请参阅 Python 文档的 4.7.4 - Unpacking Argument Lists 部分。

此外,您可以定义函数来接受 *x**y 参数,这允许函数接受在声明中未明确命名的任意数量的位置和/或命名参数。

例子:

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

s = sum(1, 2, 3, 4, 5)

或使用 **

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

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

这可以让您指定大量可选参数,而无需声明它们。

同样,您可以组合:

def sum(*values, **options):
    s = 0
    for i in values:
        s = s + i
    if "neg" in options:
        if options["neg"]:
            s = -s
    return s

s = sum(1, 2, 3, 4, 5)            # returns 15
s = sum(1, 2, 3, 4, 5, neg=True)  # returns -15
s = sum(1, 2, 3, 4, 5, neg=False) # returns 15

你为什么需要这个,函数不能在不扩展的情况下迭代提供的列表吗?
当然可以,但是您必须调用它:s = sum((1, 2, 3, 4, 5))s = sum([1, 2, 3, 4, 5])*values 选项使调用看起来像它需要许多参数,但它们被打包到函数代码的集合中。
这是真正的好处:如果您需要可变数量的参数,您可以编写原本不可能的函数。例如,C 语言的 printf 函数,它有 1+n 个参数,对于任何初级程序员来说,作为练习都很难编写。在 Python 中,初学者可以编写 def printf(string_template, *args) 并继续前进。
如果您(可能不小心:p)打开只有一个 * 而不是两个 * 的字典会发生什么?好像在做点什么,好像是一个元组出来了,但是具体是什么就不是那么明显了。 (编辑:好的,我认为答案是它只是解包键,值被丢弃)
最后一个例子意味着 * 和 ** 不仅在解包,而且在打包!查看这个出色的页面codingame.com/playgrounds/500/…
N
Ned Batchelder

一个小点:这些不是运营商。运算符在表达式中用于从现有值创建新值(例如,1+2 变为 3。这里的 * 和 ** 是函数声明和调用语法的一部分。


请注意,Python 文档在这种情况下确实将 * 称为运算符;我同意,这有点误导。
谢谢。我一直在 python 参考文档中寻找对此的明确说明,但仍然没有看到。所以函数调用的规则基本上是函数调用中表达式开头的“*”或“**”会导致这种扩展?
s
sepp2k

在函数调用中,单星将列表转换为单独的参数(例如 zip(*x)zip(x1,x2,x3) 如果 x=[x1,x2,x3] 相同),双星将字典转换为单独的关键字参数(例如 f(**k)f(x=my_x, y=my_y) 如果是 k = {'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})”。


一个(非常)迟到的评论,但我相信 def foo(*x) *x 给出了一个元组,而不是一个列表。
s
stuckoverflow

我发现这在您想要“存储”函数调用时特别有用。

例如,假设我对函数“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,它生成 Car 对象并返回它们。您可以让 myFactory.make_car('red', 'bmw', '335ix') 创建 Car('red', 'bmw', '335ix'),然后返回它。

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

当您想调用超类的构造函数时,这也很有用。


我喜欢你的例子。但是,我认为-1 + 3 == 2。
我故意在里面放了一些会失败的东西:)
M
Mark Byers

它被称为扩展调用语法。从 documentation

如果语法 *expression 出现在函数调用中,则 expression 必须计算为一个序列。此序列中的元素被视为附加的位置参数;如果有位置参数 x1,...,xN,并且表达式的计算结果为序列 y1,...,yM,则这等效于使用 M+N 个位置参数 x1,...,xN,y1,...的调用。 ..,嗯。

和:

如果语法 **expression 出现在函数调用中,则 expression 必须计算为一个映射,其内容被视为附加关键字参数。如果关键字同时出现在表达式中并作为显式关键字参数出现,则会引发 TypeError 异常。


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