目录
一、绪论
在 Python 中,单星号 * 和双星号 ** 这两个符号存在多种用法,平时不注意归纳或很少使用则容易混淆或遗忘。为此,本文将根据各类书籍、文档等,详细梳理各类用法并予以说明。
二、说明
2.1 算术运算符 —— 乘与乘方
* 为 乘法,** 为 乘方/乘幂:
>>> 2 * 4 # 乘法
8
>>> 2 ** 4 # 乘方
16
2.2 字符串运算符 —— 重复
使用乘号 * 可 重复输出 string (由于 string 是不可变类型 (imutable),* 的本质是 创建新的 string):
>>> 'cs' * 2 # 重复 2 次
'cscs'
>>> me = 'go'
>>> me * 3 # 重复 3 次
'gogogo'
2.3 函数形参汇聚 —— 打包
*args 和 **kwargs 常作为 魔法变量 出现于函数定义中,用于将不定量实参传递给函数。其中:
*args 的本质是将 位置形参 汇集为 tuple 然后由变量 args 接收,:
>>> def test1(x, *args):
''' 将除首个元素 x 外的位置参数 args 汇聚为一个 tuple '''
print(x, args)
>>> test1('a', 'b', 'c', 'd')
'a' ('b', 'c', 'd')
>>> test1(1, 2, 3, 4)
1 (2, 3, 4)
**kwargs 的本质则是将 关键字形参 汇集为 dict 然后由变量 kwargs 接收:
>>> def test2(**kwargs):
''' 将所有关键字参数汇聚为 dict '''
for key, value in kwargs.items():
print("{0} = {1}".format(key, value))
>>> test2(a=1, b=2, c=3, d=4)
a = 1
b = 2
c = 3
d = 4
注意,单星操作符 * 无法汇集关键字参数,双星操作符 ** 才可以。
若想在函数定义时 同时使用 位置形参 + 单星操作符 * + 双星操作符 ** ,则须按照如下顺序:
>>> def test3(z, *args, **kwargs):
print(z, args, kwargs)
>>> test3(0, 1, 2, 3, 4, a=1, b=2, c=3, d=4)
0 (1, 2, 3, 4) {'a': 1, 'b': 2, 'c': 3, 'd': 4}
此外,写成 *args 和 **kwargs 其实并非必须的,仅为通俗的命名约定,只有变量前的 单星号 * 或 双星号 ** 才是必须的。若有意,还可写成 *var 和 **vars 等等。
2.4 函数实参分散 —— 解包
若函数形参是定长参数,则可使用 分散操作符 (scatter operator) * 或 ** 初始化函数,类似解引用 tuple 和 dict。
例如,使用 分散操作符 * 对 tuple 实参解包:
>>> def test4(var1, var2, var3, var4):
print(var1, var2, var3, var4)
>>> variables = (1, 2, 3, 4) # 注意, 可迭代对象即可, 不限于 tuple
>>> test4(variables) # 不解包要报错
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
test4(variables)
TypeError: test4() missing 3 required positional arguments: 'var2', 'var3', and 'var4'
>>> test4(*variables) # 作用在实参、解包输入即可
1 2 3 4
再有,使用 分散操作符 ** 对 dict 实参解包:
>>> def test4(var1, var2, var3, var4):
print(var1, var2, var3, var4)
>>> variables2 = {'var1':5, 'var2':6, 'var3':7, 'var4':8} # 注意 key 对应关键字形参
>>> test4(variables2) # 不解包要报错
Traceback (most recent call last):
File "<pyshell#48>", line 1, in <module>
test4(variables2)
TypeError: test4() missing 3 required positional arguments: 'var2', 'var3', and 'var4'
>>> test4(**variables2) # 作用在实参、解包输入即可
5 6 7 8
2.5 解压可迭代对象赋值给多个变量
使用星号操作符 * 构成星号表达式能够实现对 可迭代对象 (iterable) 解压,从而将不确定个数或任意个数元素的可迭代对象 赋值给任意数量的变量。其中,使用 * 的变量将接受多余参数,并总保存在 list 中。
注意,常见的 可迭代对象 包含:序列 sequence (list、string、tuple)、set、dict、迭代器 iterator、生成器 generator、文件对象 等,而不局限于 2.4 节中的类型。有些博客、教程声称“使用 * 可以对序列解压”是不尽然的,不确切的。
>>> x, *y = [1, 2, 3, 4, 5, 6] # unpack list
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> x, *y = 'abcdefg' # unpack string
>>> x
'a'
>>> y
['b', 'c', 'd', 'e', 'f', 'g']
# -----------------------------------------------------------------------------
>>> x, *y = (1, 2, 3, 4, 5, 6) # unpack tuple
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> x, *y = {'a':1, 'b':2, 'c':3} # unpack dict
>>> x
'a'
>>> y
['b', 'c']
# -----------------------------------------------------------------------------
>>> x, *y = {1, 2, 3, 4, 5, 6} # unpack set
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> generator = (i for i in range(1,7))
>>> generator
<generator object <genexpr> at 0x00000212A48F75C8>
>>> x, *y = generator # unpack for generator
>>> x
1
>>> y
[2, 3, 4, 5, 6]
补充科普:维基百科解释,在 Python 中,迭代器 (iterator) 是遵循迭代协议的对象。使用 iter() 从任何序列对象中得到迭代器 (如 list, tuple, dict, set 等)。另一种形式的输入迭代器是生成器 (generator)。很多容器如 list、string 可用 for 循环遍历对象。for 语句会调用容器对象中的 iter() 函数,该函数返回一个定义了 __next__() 方法的迭代器对象,该方法将逐一访问容器中的元素。故在 Python中,任意对象只要定义了__next__ 方法,就是一个迭代器。因此,Python 容器如 list、string、tuple、set、dict 实际上都可以被称作迭代器。关于可迭代对象和迭代器,详见:链接。
此外,单星号 * 对于解压、分割 string 很有用:
>>> line = 'nobody:*:-4:-4:Unprivileged User:/var/empty:/usr/bin/true'
>>> uname, *fields, homedir, sh = line.split(':') # string 分割 + 解包
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/true'
# -----------------------------------------------------------------------------
>>> record = ('Child', 66, 12.34, (1, 6, 2020))
>>> name, *_, (*_, year) = record # 使用占位符如 _ 来接收无用的部分
>>> name
'Child'
>>> year
2020
如果够聪明,还能用这种分割语法巧妙实现递归算法:
>>> items = [1, 10, 7, 4, 5, 9]
>>> def new_sum(items): # 其实因语言层面限制, 递归非 Python 所长
head, *tail = items
return head + sum(tail) if tail else head
>>> new_sum(items)
36
参考资料:
《Python Cookbook》、《Intermediate Python》、《Think Python》
https://www.runoob.com/python3/python3-basic-operators.html
https://www.runoob.com/w3cnote/python-one-and-two-star.html