![Effective Python:编写高质量Python代码的90个有效方法(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/417/39980417/b_39980417.jpg)
第13条 通过带星号的unpacking操作来捕获多个元素,不要用切片
基本的unpacking操作(参见第6条)有一项限制,就是必须提前确定需要拆解的序列的长度。例如,销售汽车的时候,我们可能会把每辆车的车龄写在一份列表中,然后按照从大到小的顺序排列好。如果试着通过基本的unpacking操作获取其中最旧的两辆车,那么程序运行时会出现异常。
![060-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/060-02.jpg?sign=1739295471-KWRkOqpR0Kq6GrNvl5xNUfC1MMFmSQ1Z-0-f7cb323e1bf3c1299476913608f4545c)
Python新手经常通过下标与切片(参见第11条)来处理这个问题。例如,可以明确通过下标把最旧和第二旧的那两辆车取出来,然后把其余的车放到另一份列表中。
![060-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/060-03.jpg?sign=1739295471-azNGVHjVZelmRepP5H9kbibUSIf4Gib7-0-aaaf0c1474d13c34e030960d91402263)
这样做没问题,但是下标与切片会让代码看起来很乱。而且,用这种办法把序列中的元素分成多个子集合,其实很容易出错,因为我们通常容易把下标多写或少写一个位置。例如,若修改了其中一行,但却忘了更新另一行,那就会遇到这种错误。
这个问题通过带星号的表达式(starred expression)来解决会更好一些,这也是一种unpacking操作,它可以把无法由普通变量接收的那些元素全都囊括进去。下面用带星号的unpacking操作改写刚才那段代码,这次既不用取下标,也不用做切片。
![061-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-01.jpg?sign=1739295471-3IKP4KeXEt7FF7Oa4luPFyYa2FZCFdrT-0-59941f018696292088e07bb38c604cc7)
这样写简短易读,而且不容易出错,因为它不要求我们在修改完其中一个下标之后,还必须记得同步更新其他的下标。
这种带星号的表达式可以出现在任意位置,所以它能够捕获序列中的任何一段元素。
![061-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-02.jpg?sign=1739295471-fLV4U115QrBdmgu2pql6CYxCvNcAI4Ue-0-4ee1a56f11c97d42a8fb1637b0fa4b03)
只不过,在使用这种写法时,至少要有一个普通的接收变量与它搭配,否则就会出现SyntaxError
。例如不能像下面这样,只使用带星号的表达式而不搭配普通变量。
![061-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-03.jpg?sign=1739295471-vhmn0PxDGnF3FfCT0d6GugaBVMti69KU-0-6db59f6b3c67968c920c9ddf2cadc50c)
另外,对于单层结构来说,同一级里面最多只能出现一次带星号的unpacking。
![061-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-04.jpg?sign=1739295471-Sp1DLVGVebqs3WRyk1GvkdXRU2GnY6K1-0-59d1b3b72e7a263a231cad8102511e52)
如果要拆解的结构有很多层,那么同一级的不同部分里面可以各自出现带星号的unpacking操作。当然笔者并不推荐这种写法(类似的建议参见第19条)。这里举这样一个例子,是想帮助大家理解这种带星号的表达式可以实现怎样的拆分效果。
![061-05](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-05.jpg?sign=1739295471-RuY5wpgDDYFpiHD54fA0h3F64vYWuqHY-0-4e0cbce537aec7f39bc8af2e1b3570b0)
带星号的表达式总会形成一份列表实例。如果要拆分的序列里已经没有元素留给它了,那么列表就是空白的。如果能提前确定有待处理的序列里至少会有N个元素,那么这项特性就相当有用。
![062-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-01.jpg?sign=1739295471-2ftF3vdGU1gOTEmK2IWGrBTDTIEXKp3A-0-fc39908b7821d1e4dfa5b7a8fa41b319)
unpacking操作也可以用在迭代器上,但是这样写与把数据拆分到多个变量里面的那种基本写法相比,并没有太大优势。例如,我可以先构造长度为2的取值范围(range
),并把它封装在it
这个迭代器里,然后将其中的值拆分到first
与second
这两个变量里。但这样写还不如直接使用形式相符的静态列表(例如[1, 2]
),那样更简单。
![062-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-02.jpg?sign=1739295471-tc6D0alo1USKLfmznxxa0WzOKE6aQq1v-0-d3d1fd12f4ef785ca705a6246c5901aa)
对迭代器做unpacking操作的好处,主要体现在带星号的用法上面,它使迭代器的拆分值更清晰。例如,这里有个生成器,每次可以从含有整个一周的汽车订单的CSV文件中取出一行数据。
![062-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-03.jpg?sign=1739295471-nXaOOan9z2wMlzv55c91VN5yTOhrmHYz-0-0d14fbcd870d8fa5de86d0a2e86fd5a5)
我们可以用下标和切片来处理这个生成器所给出的结果,但这样写需要很多行代码,而且看着比较混乱。
![062-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-04.jpg?sign=1739295471-tYCMAEKTe0HkR52Fp7bKQsYMkgTDQBaV-0-3a11fb4310d19445d7a93316de305330)
利用带星号的unpacking操作,我们可以把第一行(表头)单独放在header
变量里,同时把迭代器所给出的其余内容合起来表示成rows
变量。这样写就清楚多了。
![063-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/063-01.jpg?sign=1739295471-54Eq1VC2Tp0av3ot9qpTNIYrldJkC4I0-0-d0e89ebc0e5ed0f2791bb44affab8696)
带星号的这部分总是会形成一份列表,所以要注意,这有可能耗尽计算机的全部内存并导致程序崩溃。首先必须确定系统有足够的内存可以存储拆分出来的结果数据,然后才可以对迭代器使用带星号的unpacking操作(还有另一种做法,参见第31条)。
要点
- 拆分数据结构并把其中的数据赋给变量时,可以用带星号的表达式,将结构中无法与普通变量相匹配的内容捕获到一份列表里。
- 这种带星号的表达式可以出现在赋值符号左侧的任意位置,它总是会形成一份含有零个或多个值的列表。
- 在把列表拆解成互相不重叠的多个部分时,这种带星号的unpacking方式比较清晰,而通过下标与切片来实现的方式则很容易出错。