【Python】详解 单星号操作符 (*) 与 双星号操作符 (**)

目录

一、绪论

二、说明

2.1 算术运算符 —— 乘与乘方

2.2 字符串运算符 —— 重复

2.3 函数形参汇聚 —— 打包

2.4 函数实参分散 —— 解包

2.5 解压可迭代对象赋值给多个变量


一、绪论


        在 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

        https://www.jianshu.com/p/2acee6392be5

        https://zhuanlan.zhihu.com/p/76831058

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值