引言
有些小伙伴有其他编程语言的学习、使用的经验,然后迁移到Python。一般会比完全的新手小白,更快速地把Python用起来。这是他们的优势,但也是他们的劣势。
之所以这么说,是因为从其他编程语言带过来的,除了相通的编程思维、框架性的东西,还有编程语言的使用习惯。
而这些其他编程语言中的使用习惯,就会导致他们写出来的Python代码不是那么的Pythonic,当然,这是一种感觉。
虽说我们首要追求的是能用、够用。但是,偶尔也要稍微留意一下。就好像学习英语一样,虽然不追求能操着一口正宗的英伦腔,但也不应该对自己的chinglish迷之自信。
此外,故步自封于其他编程语言中的编程习惯,也可能限制我们更加灵活、便捷地发挥Python中的强大特性。
变量值交换
其他语言中,如果需要交换两个变量,通常需要引入一个中间变量,比如:
a = 5
b = 10
c = b
b = a
a = c
在Python中还这样写,就显得有些冗余、不够地道了。
其实,在Python中有一种更加简便的写法:
x = 5
y = 10
x, y = y, x
不需要引入中间变量,直接完成变量值的交换。我看有些地方把Python中的这种变量值交换,称之为unpacking机制。所以,我在今天的这篇讲解unpacking机制的文章中提到了这一点。
但是,也许是Python不同版本、Python解释器实现的差异,在我的Python3.11、CPython解释器的环境下,查看对应的字节码,发现并没有用到unpacking机制,而是Python中提供了一个用于进行栈顶两个值交换的swap指令。对应的字节码指令及解释,如下图所示:感兴趣的可以自行查看自己环境中变量值交换的实现。
基础unpacking机制
接下来,说回今天的主题,unpacking机制。
既然是unpacking,有些地方翻译为拆包,自然首先要有包可拆。这里所谓的拆包,其实是针对容器/集合类型的数据结构来说的。
通常情况下,我们把一个列表、元组或者是字典中的元素取出来,可以使用下标索引的方式。
比如,有如下场景:
我们有一些人员信息存放在一个列表中,每个列表元素是一个元组,元组中的元素,分别是姓名、年龄、性别。
现在,我们需要遍历人员信息的这个列表,然后将人员信息进行格式化打印输出。
使用下标索引的方式,可以这样实现:
persons = [('张三', 18, '女'), ('小红', 23, '男')]
for p in persons:name = p[0]age = p[1]gender = p[2]print(f"姓名: {name}, 年龄: {age}, 性别: {gender}")
但是,这样的写法,不够地道,没有使用Python给我们提供的更加好用的写法。
接下来,我们用unpacking的方式重新写一下:
# unpacking
persons = [('张三', 18, '女'), ('小红', 23, '男')]
for p in persons:name, age, gender = pprint(f"姓名: {name}, 年龄: {age}, 性别: {gender}")
这样写下来,用到了Python中的unpacking,首先代码行数减少了。
是否真的应用到了unpacking机制,还是说跟变量值交换一样,也是人云亦云,我们可以看下对应的字节码序列:
字节码指令序列的其他指令可以不用关心,我们重点看源码第4行的对应指令序列,可以看到:
1、确实触发了unpacking机制;
2、UNPACK_SEQUENCE指令,用于将栈顶的集合变量进行拆包的操作,拆为多少个,指令有一个操作数,此时是3,由操作数决定;
3、拆包指令实现的结果是,栈顶集合变量出栈,按照操作数拆分,然后依次入栈。
unpacking的机制,看似好用,但是,细心的你,可能立马会发现一个问题,如果集合中元素有很多个,此时,我们只需要其中的一部分,怎么办呢。
解决的方法,就是我们在前面的文章中提到过的占位符_的用法。
比如,我们当前,只需要姓名、性别,可以这样改写:
# unpacking
persons = [('张三', 18, '女'), ('小红', 23, '男')]
for p in persons:name, _, gender = pprint(f"姓名: {name}, 性别: {gender}")
所有我们不需要的元素,都可以用占位符进行舍弃,但是,占位符_只是省去了我们给变量取名的麻烦,不需要的元素比较多的时候,似乎还是不太方便,反而不如索引操作方便,好在Python中提供了对应的解决方案。
扩展的unpacking
带的变量,在Python中为可扩展的变量。
如下,为带的unpacking的写法:
# * unpacking
persons = [('张三', 18, 190, '女'), ('小红', 23, 165, '男')]
for p in persons:name, *others, gender = pprint(others)print(type(others))print(f"姓名: {name}, 性别: {gender}")
代码中,会将除了name接收的第一个元素,以及gender接收的最后一个元素,之外的所有元素,封装为一个列表,由others接收。
从对应的字节码,可以看出有些不同:
首先,翻译为字节码指令序列时,多了EXTENDED_ARG指令,用于扩展变量others;其次拆包的指令,从之前的UNPACK_SEQUENCE变成了UNPACK_EX。
注意,关于带星号定义的可扩展变量,可以理解为不定长列表,可以接收零个或者多个值,后续在函数的定义中也会用到。