![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=1739032673-gsHbCojWwDRb8L67YN7OV0qBsdqldHUB-0-f3e59c18fe4f3b9897a872e7a1676e17)
Python新手经常通过下标与切片(参见第11条)来处理这个问题。例如,可以明确通过下标把最旧和第二旧的那两辆车取出来,然后把其余的车放到另一份列表中。
![060-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/060-03.jpg?sign=1739032673-9mxpOCeYrbyraDjQz9kQRTRgR1XE7wdH-0-a9f75163f93ea078725ab26c8bd4715c)
这样做没问题,但是下标与切片会让代码看起来很乱。而且,用这种办法把序列中的元素分成多个子集合,其实很容易出错,因为我们通常容易把下标多写或少写一个位置。例如,若修改了其中一行,但却忘了更新另一行,那就会遇到这种错误。
这个问题通过带星号的表达式(starred expression)来解决会更好一些,这也是一种unpacking操作,它可以把无法由普通变量接收的那些元素全都囊括进去。下面用带星号的unpacking操作改写刚才那段代码,这次既不用取下标,也不用做切片。
![061-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-01.jpg?sign=1739032673-stzOk38MdDYH46ZwwfklXowoKQA1MJPR-0-013499a1a0b1f2e1d3dfea19de817174)
这样写简短易读,而且不容易出错,因为它不要求我们在修改完其中一个下标之后,还必须记得同步更新其他的下标。
这种带星号的表达式可以出现在任意位置,所以它能够捕获序列中的任何一段元素。
![061-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-02.jpg?sign=1739032673-jdfhrQEaoQnoFx4pdpC60dNQ5gfZ1vkv-0-30ec9566cb1d8ca5a87d8767bb6c34da)
只不过,在使用这种写法时,至少要有一个普通的接收变量与它搭配,否则就会出现SyntaxError
。例如不能像下面这样,只使用带星号的表达式而不搭配普通变量。
![061-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-03.jpg?sign=1739032673-H3oi5XR0sQ5RFTzLfBWumyUBGWr7ncT2-0-606f816e80af63fcc3115851bf4df69f)
另外,对于单层结构来说,同一级里面最多只能出现一次带星号的unpacking。
![061-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-04.jpg?sign=1739032673-9TYOCLpFtI4uJMOECzojpH89HutQTob7-0-b2e10b05f4775061a95947e60fdde34b)
如果要拆解的结构有很多层,那么同一级的不同部分里面可以各自出现带星号的unpacking操作。当然笔者并不推荐这种写法(类似的建议参见第19条)。这里举这样一个例子,是想帮助大家理解这种带星号的表达式可以实现怎样的拆分效果。
![061-05](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/061-05.jpg?sign=1739032673-xLFgh8xhVILmZY8XVjABinmrGhhEqkW5-0-28971c8e50a7bf07519a954d6ac97c1a)
带星号的表达式总会形成一份列表实例。如果要拆分的序列里已经没有元素留给它了,那么列表就是空白的。如果能提前确定有待处理的序列里至少会有N个元素,那么这项特性就相当有用。
![062-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-01.jpg?sign=1739032673-yiY0WEY3Slda2LViiFTzlxwQUTddahys-0-01035bcd971eddc0f25b52e5e0787f75)
unpacking操作也可以用在迭代器上,但是这样写与把数据拆分到多个变量里面的那种基本写法相比,并没有太大优势。例如,我可以先构造长度为2的取值范围(range
),并把它封装在it
这个迭代器里,然后将其中的值拆分到first
与second
这两个变量里。但这样写还不如直接使用形式相符的静态列表(例如[1, 2]
),那样更简单。
![062-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-02.jpg?sign=1739032673-BeUHoQSEGJa9Tz7AJY8TKP21psJOXHg6-0-848aa83f2d344fa6f1454768213d867b)
对迭代器做unpacking操作的好处,主要体现在带星号的用法上面,它使迭代器的拆分值更清晰。例如,这里有个生成器,每次可以从含有整个一周的汽车订单的CSV文件中取出一行数据。
![062-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-03.jpg?sign=1739032673-P6032uK2bUQVK9BdWGeCxIWDc1dRFUKF-0-567309a1301b49a70f11b88a2f7efc6b)
我们可以用下标和切片来处理这个生成器所给出的结果,但这样写需要很多行代码,而且看着比较混乱。
![062-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/062-04.jpg?sign=1739032673-uhZsVeH8O4Y4WLBw6RKuM3I9Dj0ynDtg-0-427638a38c775bd280333df3540cffb3)
利用带星号的unpacking操作,我们可以把第一行(表头)单独放在header
变量里,同时把迭代器所给出的其余内容合起来表示成rows
变量。这样写就清楚多了。
![063-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/063-01.jpg?sign=1739032673-N1hXxe3u65YHUojixu0jRyCTWsHFLKZM-0-aef50dc1c889ddb1bd77308395cbf46f)
带星号的这部分总是会形成一份列表,所以要注意,这有可能耗尽计算机的全部内存并导致程序崩溃。首先必须确定系统有足够的内存可以存储拆分出来的结果数据,然后才可以对迭代器使用带星号的unpacking操作(还有另一种做法,参见第31条)。
要点
- 拆分数据结构并把其中的数据赋给变量时,可以用带星号的表达式,将结构中无法与普通变量相匹配的内容捕获到一份列表里。
- 这种带星号的表达式可以出现在赋值符号左侧的任意位置,它总是会形成一份含有零个或多个值的列表。
- 在把列表拆解成互相不重叠的多个部分时,这种带星号的unpacking方式比较清晰,而通过下标与切片来实现的方式则很容易出错。