
第13条 通过带星号的unpacking操作来捕获多个元素,不要用切片
基本的unpacking操作(参见第6条)有一项限制,就是必须提前确定需要拆解的序列的长度。例如,销售汽车的时候,我们可能会把每辆车的车龄写在一份列表中,然后按照从大到小的顺序排列好。如果试着通过基本的unpacking操作获取其中最旧的两辆车,那么程序运行时会出现异常。

Python新手经常通过下标与切片(参见第11条)来处理这个问题。例如,可以明确通过下标把最旧和第二旧的那两辆车取出来,然后把其余的车放到另一份列表中。

这样做没问题,但是下标与切片会让代码看起来很乱。而且,用这种办法把序列中的元素分成多个子集合,其实很容易出错,因为我们通常容易把下标多写或少写一个位置。例如,若修改了其中一行,但却忘了更新另一行,那就会遇到这种错误。
这个问题通过带星号的表达式(starred expression)来解决会更好一些,这也是一种unpacking操作,它可以把无法由普通变量接收的那些元素全都囊括进去。下面用带星号的unpacking操作改写刚才那段代码,这次既不用取下标,也不用做切片。

这样写简短易读,而且不容易出错,因为它不要求我们在修改完其中一个下标之后,还必须记得同步更新其他的下标。
这种带星号的表达式可以出现在任意位置,所以它能够捕获序列中的任何一段元素。

只不过,在使用这种写法时,至少要有一个普通的接收变量与它搭配,否则就会出现SyntaxError
。例如不能像下面这样,只使用带星号的表达式而不搭配普通变量。

另外,对于单层结构来说,同一级里面最多只能出现一次带星号的unpacking。

如果要拆解的结构有很多层,那么同一级的不同部分里面可以各自出现带星号的unpacking操作。当然笔者并不推荐这种写法(类似的建议参见第19条)。这里举这样一个例子,是想帮助大家理解这种带星号的表达式可以实现怎样的拆分效果。

带星号的表达式总会形成一份列表实例。如果要拆分的序列里已经没有元素留给它了,那么列表就是空白的。如果能提前确定有待处理的序列里至少会有N个元素,那么这项特性就相当有用。

unpacking操作也可以用在迭代器上,但是这样写与把数据拆分到多个变量里面的那种基本写法相比,并没有太大优势。例如,我可以先构造长度为2的取值范围(range
),并把它封装在it
这个迭代器里,然后将其中的值拆分到first
与second
这两个变量里。但这样写还不如直接使用形式相符的静态列表(例如[1, 2]
),那样更简单。

对迭代器做unpacking操作的好处,主要体现在带星号的用法上面,它使迭代器的拆分值更清晰。例如,这里有个生成器,每次可以从含有整个一周的汽车订单的CSV文件中取出一行数据。

我们可以用下标和切片来处理这个生成器所给出的结果,但这样写需要很多行代码,而且看着比较混乱。

利用带星号的unpacking操作,我们可以把第一行(表头)单独放在header
变量里,同时把迭代器所给出的其余内容合起来表示成rows
变量。这样写就清楚多了。

带星号的这部分总是会形成一份列表,所以要注意,这有可能耗尽计算机的全部内存并导致程序崩溃。首先必须确定系统有足够的内存可以存储拆分出来的结果数据,然后才可以对迭代器使用带星号的unpacking操作(还有另一种做法,参见第31条)。
要点
- 拆分数据结构并把其中的数据赋给变量时,可以用带星号的表达式,将结构中无法与普通变量相匹配的内容捕获到一份列表里。
- 这种带星号的表达式可以出现在赋值符号左侧的任意位置,它总是会形成一份含有零个或多个值的列表。
- 在把列表拆解成互相不重叠的多个部分时,这种带星号的unpacking方式比较清晰,而通过下标与切片来实现的方式则很容易出错。