*
运算符在 Python 中是什么意思,例如在 zip(*x)
或 f(**k)
之类的代码中?
它在解释器内部是如何处理的?它会影响性能吗?它是快还是慢?什么时候有用,什么时候没用?它应该用在函数声明还是调用中?
*
和 **
运算符。
[*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。
单星 *
将序列/集合解压缩为位置参数,因此您可以这样做:
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
一个小点:这些不是运营商。运算符在表达式中用于从现有值创建新值(例如,1+2 变为 3。这里的 * 和 ** 是函数声明和调用语法的一部分。
在函数调用中,单星将列表转换为单独的参数(例如 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 给出了一个元组,而不是一个列表。
我发现这在您想要“存储”函数调用时特别有用。
例如,假设我对函数“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)
当您想调用超类的构造函数时,这也很有用。
它被称为扩展调用语法。从 documentation:
如果语法 *expression 出现在函数调用中,则 expression 必须计算为一个序列。此序列中的元素被视为附加的位置参数;如果有位置参数 x1,...,xN,并且表达式的计算结果为序列 y1,...,yM,则这等效于使用 M+N 个位置参数 x1,...,xN,y1,...的调用。 ..,嗯。
和:
如果语法 **expression 出现在函数调用中,则 expression 必须计算为一个映射,其内容被视为附加关键字参数。如果关键字同时出现在表达式中并作为显式关键字参数出现,则会引发 TypeError 异常。
apply()
函数实现了相同的功能
s = sum((1, 2, 3, 4, 5))
或s = sum([1, 2, 3, 4, 5])
,*values
选项使调用看起来像它需要许多参数,但它们被打包到函数代码的集合中。