Python 星号表达式、单下划线变量

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 语言而言,单下划线没有特殊意义,它是一个有效的标识符,就像 _foofoo_ 或 _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 的用法
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高亚奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值