Python 星号表达式
Python 星号表达式(Starred Expression)的形式有 *
、*args
、**
和 **kwargs
,用于将可迭代的数据或者参数序列按一定的数据形式解析出来。
快速理解
关于星号表达式的理解,可以简单地认为:
- 它能将可迭代的数据(iterable,由多全元素组成、能拆分的数据类型)拆开一系列独立元素;
- 拆开时要指定整体的数据类型(就是将这一个个元素最终形成什么数据结构);
- 一个星号是拆开序列(类似列表),两个星号是拆开字典(mapping),即
*iterable
和**dictionary
; *args
和**kwargs
星号后为什么有变量名呢?因为在作为函数的参数时需要有变量名去使用,这两个变量名是约定俗成,你用别的也可以(不建议用别的)。
马上进入例子:
h = 'gairuo' # 一个普通字符串,可以拆开(迭代)
[*h] # ['g', 'a', 'i', 'r', 'u', 'o'] 列表
{*h} # {'a', 'g', 'i', 'o', 'r', 'u'} 集合
# 将两个元组合并为一个元组
x,y = ('a','b'), (1,2)
(*x,*y) # ('a', 'b', 1, 2)
# 合并字典
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
# 拆成字典(还是自己)
{**x}
# {'a': 1, 'b': 2}
{**x, **y} # 合并
# {'a': 1, 'b': 3, 'c': 4}
{**y, **x} # 换位置合并(其实字典一般不用在意位置)
# {'b': 2, 'c': 4, 'a': 1}
没看明白没关系,下边会详细介绍。
解包 unpack
星号表达式一个最最要的作用是解包(unpack),解包就是把一个整体数据按每个元素拆开。
print(*[1], *[2,3], *(4, 5), 6)
# 1 2 3 4 5 6
dict(**{'x1': 0, 'x2': 1}, y=2, **{'z': 3})
# {'x1': 0, 'x2': 1, 'y': 2, 'z': 3}
上例中,第一个语句将两个列表和一个元组拆开,最终返回的是单个元素;第二行代码将字典拆开,拆开后它与其他字典的定义组成了一个新的字典,完成了字典合并的工作。
我们发现,解包操作有以下特点:
- 被解包的数据必须是可迭代的,可以是字符串、元组、列表、字典、array、Series 等等;
- 解包后要有一定的数据结构去承接(接纳),如果单纯用星号是错误的(如
*a
,应该是[*a]
); *iterable
和**dictionary
对应解包的是无 key 的和 有 key (mapping)的;
以下是一个利用 Pandas 的综合例子:
import pandas as pd
dict(**pd.Series([*'abcd'], index=[*'1234']))
# {'1': 'a', '2': 'b', '3': 'c', '4': 'd'}
构建 Series 时的索引和值都可以将字符串进行解包成列表,由于 Series 是一个有key (mapping)的数据结构,我们可以将其用两个星号解包成字典。
一些其他的例子,方便大家理解:
# 在元组、列表、集合和字典允许解包
*range(4), 4
# (0, 1, 2, 3, 4)
[*range(4), 4]
# [0, 1, 2, 3, 4]
{*range(4), 4}
# {0, 1, 2, 3, 4}
{'x': 1, **{'y': 2}}
# {'x': 1, 'y': 2}
# 在字典中后面的值将始终覆盖前面的值
{'x': 1, **{'x': 2}}
# {'x': 2}
{**{'x': 2}, 'x': 1}
# {'x': 1}
# 推导式
ranges = [range(i) for i in range(5)]
[[*item] for item in ranges]
# [[], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3]]
{i:[*item] for i, item in enumerate(ranges)}
# {0: [], 1: [0], 2: [0, 1], 3: [0, 1, 2], 4: [0, 1, 2, 3]}
a = [1,2]
'{}+{}={}'.format(*a, sum(a))
# '1+2=3'
合并:
a = [1, 2]
b = (3, 4)
c = a + b
# 报错
c = [*a, *b]
c
# [1, 2, 3, 4]
定义变量
星号表达式在我们定义变量时也可以让我们的操作非常简便,利用它在给变量赋值时,可以将变量成为一个容器(catch-all)来容纳未分配有明确定义的内容。
a, *b, c = range(5)
a # 0
c # 4
b # [1, 2, 3]
以上,b 使用了星号表达式,就容纳了中间所有的元素形成了一个列表。这就实现了类似:
first, rest = seq[0], seq[1:]
first, *rest = seq # 效果同上
减少了代码量,更加便捷。对于更复杂的解包模式,新语法看起来更干净,不再需要笨拙的索引处理。
此外,如果右边的值不是列表,而是 iterable,则必须将其转换为列表才能进行切片。
参数传递
定义函数时,经常会使用 *args
和 **kwargs
来声明元组变量(位置变量)和字典变量(关键字变量)。
args = [1, 3]
range(*args) # 这样就将两个位置变量传入
# range(1, 3)
def f(a, b, c, d):
print(a, b, c, d, sep = '&')
f(1,2,3,4)
# 1&2&3&4
f(*[1, 2, 3, 4])
# 1&2&3&4
其他
优点有以下:
- 方便,减少写 for 循环或者 推导式;
缺点:
- 初学者难理解
注意:
- 星号表达式不可单独使用
特别要注意的是对于无限元素迭代器,千万不要用解包,用 next(),如:
import itertools
c = itertools.count(10, 2)
[*c] # ! 危险,会死循环,按Ctrl+C退出
单下划线变量
就 Python 语言而言,单下划线没有特殊意义,它是一个有效的标识符,就像 _foo
、foo_
或 _f_o_o_
一样。但因其在一些特殊场景下的用途我们值得介绍一下。单下划线的任何特殊含义都完全是按照惯例,并不是一种强制的要求,我们知道了这些很常见惯例才能读懂别人的代码。
原理
交互式解释器将最后一次求值的结果保存 _
变量中,它与打印等内置功能一起存储在内置模块中。在其他地方, _
是常规标识符,它通常用于命名“特殊”项,但它对 Python 本身来说并不特殊。这一先例是由标准的CPython 解释器开创,其他解释器也纷纷效仿,支持了这个特性。
软关键字
在 Python 3.10 及以上版本,在 match 语句中的 case 模式中,_
是表示通配符的软关键字。
常见使用场景
不打算使用变量,但语法/语义需要名称时的伪装:
# 迭代忽略不用的内容
sum(1 for _ in some_iterable)
for _ in range(10):
do_something()
# 不考虑特定元素的拆包
head, *_ = values
# 函数忽略其参数
def callback(_):
return True
由于名称的查找方式,除非有全局或局部定义的影响,_
定义在 builtins._
,注意:某些 shell(如 ipython)有不指定给内置 builtins._
的特殊情况。
>>> 42
42
>>> f'the last answer is {_}'
'the last answer is 42'
>>> _
'the last answer is 42'
>>> _ = 4 # shadow ``builtins._`` with global ``_``
>>> 23
23
>>> _
4
在国际化和本地化上下文中,用作主要翻译功能的别名(https://docs.python.org/3/library/gettext.html#localizing-your-module)。
raise forms.ValidationError(_("Please enter a correct username"))
数字可以用单下划线连接,用做千分位,增加可读性:
243_343_343
(这个其实和下划线没啥关系)
需要注意的是在交互编码器中,变量需要执行返回值操作,才能赋值给下划线:
a = 8
a
# 8
_
# 8
# 注意,看下文解释
b = 9
_
# 8
_
# 8
这里的下划线不会取 b 因为上次的执行结果是 a 的值,所以仍然是 a 的值 8。
总结
在Python中使用下划线有5种情况。
- 用于在解释器中存储最后一个表达式的值。
- 用于忽略特定值(所谓“我不在乎”)
- 赋予变量或函数名特殊的含义和函数
- 用作“国际化(i18n)”或“本地化(l10n)”功能
- 分隔数字文字值的数字(其实和下划线没啥关系)
两个下划线和三个下划线
上文讲了在交互式解释器下,一个下划线代表上个值,那么两个和三个下划线呢?
a = 1
a
# 1
b = 2
b
# 2
c = 3
c
# 3
# 两个下划线
__
# 2
可见,两个下划线__
为倒数第二个执行值,那么三个下划线呢?
a = 1
a
# 1
b = 2
b
# 2
c = 3
c
# 3
# 三个下划线
___
# 1
三个下划线 ___
则是倒数第三个执行值。
四个下划线由于没有定义会报错:
____
# NameError: name '____' is not defined
以上在 Jupyter 上测试,是 ipython 交互式解释器的功能,非常方便。
其他
下划线的使用,如同:
_
在 Prolog 的用法~
在 Matlab 的用法