【问题标题】:What does the star and doublestar operator mean in a function call?函数调用中的星号和双星号运算符是什么意思?
【发布时间】:2011-02-24 16:43:48
【问题描述】:

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

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

【问题讨论】:

标签: python syntax parameter-passing iterable-unpacking argument-unpacking


【解决方案1】:

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

【讨论】:

  • 一个(非常)迟到的评论,但我相信来自def foo(*x) *x 给出了一个元组,而不是一个列表。
【解决方案2】:

称为扩展调用语法。来自documentation

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

和:

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

【讨论】:

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

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

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/…
【解决方案4】:

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

【讨论】:

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

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

例如,假设我对函数 '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。
  • 我故意在里面放了一些会失败的东西:)
猜你喜欢
  • 1970-01-01
  • 2015-03-26
  • 1970-01-01
  • 2014-03-10
  • 1970-01-01
  • 2021-09-21
相关资源
最近更新 更多