![Effective Python:编写高质量Python代码的90个有效方法(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/417/39980417/b_39980417.jpg)
第6条 把数据结构直接拆分到多个变量里,不要专门通过下标访问
Python内置的元组(tuple
)类型可以创建不可变的序列,把许多元素依次保存起来。最简单的用法是只用元组保存两个值,例如字典里面的键值对。
![039-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/039-04.jpg?sign=1738880641-m2MI1KtGgi3fjCjhpVZdw43g0xRatH20-0-c4fe3a037aa32effae6016c3ebe8a8bb)
我们可以用整数作下标,通过下标来访问元组里面对应的元素。
![040-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/040-01.jpg?sign=1738880641-mZx0GSAVNj0lMfNaDnbPhFBR1yZIFQaw-0-535171c639025b9b4c8984f65cf5f2bd)
创建好tuple
之后,就不能通过下标给其中的元素赋新值了。
![040-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/040-02.jpg?sign=1738880641-m3O6I69UPBLcJFXudMBOMCD8p3POfHoc-0-51d5be5b10c18766dd93ac3b2cf39220)
Python还有一种写法,叫作拆分(unpacking)。这种写法让我们只用一条语句,就可以把元组里面的元素分别赋给多个变量。对元组做unpacking的时候,有人觉得,这好像是要修改它里面的元素,其实并不是这样。元组的元素本身不能修改,但是可以通过unpacking把这些元素分别赋给相应的变量,那些变量是可以修改的。例如,若我们确定某个元组里面只有两个元素,那么就可以直接把这两个元素分别赋给相应的变量,而不用再通过下标去访问。
![040-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/040-03.jpg?sign=1738880641-Ni6rVJPR0bva4PN5IbQxi0AMPch397qD-0-012b9d23d0e885e230a1e0acd96f4db6)
通过unpacking来赋值要比通过下标去访问元组内的元素更清晰,而且这种写法所需的代码量通常比较少。当然,赋值操作的左边除了可以罗列单个变量,也可以写成列表、序列或任意深度的可迭代对象(iterable)。例如,下面这种写法就是成立的,然而笔者并不推荐大家在代码里这样写,举这个例子只是想让大家了解Python支持这样的写法,以及这种写法为什么可以成立。
![040-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/040-04.jpg?sign=1738880641-sjdRFfxoov2YKnOwnLqi0NMzL9HeBl3E-0-edcbf17c12daae18b56536c3b00e4d3d)
Python新手可能还不知道,其实我们可以通过unpacking原地交换两个变量,而不用专门创建临时变量。先来看下面这种传统的写法,这个升序排序算法用下标的形式来访问有待排序的这个a
列表,并且通过temp
变量交换其中两个相邻的元素。
![041-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/041-01.jpg?sign=1738880641-6boAn6X8KoT7WklkDdk2FjG0W6CahSdZ-0-2aa3f72b3dffcd77d089d0503e4eeaac)
有了unpacking机制之后,只需要写一行代码就可以交换这两个元素,而不用像刚才那样分成三行来写。
![041-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/041-02.jpg?sign=1738880641-qxg2eI87NtpRBZS5QUIHMzlA7lGwzC9r-0-59d9ddea9379904817debb49eaf500d3)
这样写为什么可以成立呢?因为Python处理赋值操作的时候,要先对=号右侧求值,于是,它会新建一个临时的元组,把a[i]
与a[i-1]
这两个元素放到这个元组里面。例如,头一次进入内部的for
循环时,这两个元素分别是'carrots'
与'pretzels'
,于是,系统就会创建出('carrots'
, 'pretzels')
这样一个临时的元组。然后,Python会对这个临时的元组做unpacking,把它里面的两个元素分别放到=
号左侧的那两个地方,于是,'carrots'
就会把a[i-1]
里面原有的'pretzels'
换掉,'pretzels'
也会把a[i]
里面原有的'carrots'
换掉。现在,出现在a[0]
这个位置上面的字符串就是'carrots'
了,出现在a[1]
这个位置上面的字符串则是'pretzels'
。做完unpacking后,系统会扔掉这个临时的元组。
unpacking机制还有一个特别重要的用法,就是可以在for
循环或者类似的结构(例如推导与生成表达式,这些内容参见第27条)里面,把复杂的数据拆分到相关的变量之中。下面这段代码没有使用unpacking机制,而是采用传统的写法来迭代snacks
列表里面的元素。
![042-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/042-01.jpg?sign=1738880641-yZCmy7z1sTubOpgJ8Zr04ek9E4UxXaLt-0-f8a08535f1723825c07e9fc6f485abfe)
这样写虽然没错,但看起来很乱,因为snacks
结构本身并不是一份简单的列表,它的每个元素都是一个元组,所以必须逐层访问才能查到最为具体的数据,也就是每种零食的名称(name
)及卡路里(calories
)。下面换一种写法,首先调用内置的enumerate
函数(参见第7条)获得当前要迭代的元组,然后针对这个元组做unpacking,这样就可以直接得到具体的name
与calories
值了。
![042-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/042-02.jpg?sign=1738880641-GMJ9wDWJSnOaAyriuq4eLt7OOCyyVl6C-0-b9459daade0256525611674dc4f21356)
这才是符合Python风格的写法(Pythonic式的写法),我们不需要再通过下标逐层访问了。这种写法可以节省篇幅,而且比较容易理解。
Python的unpacking机制可以用在许多方面,例如构建列表(参见第13条)、给函数设计参数列表(参见第22条)、传递关键字参数(参见第23条)、接收多个返回值(参见第19条)等。
明智地使用unpacking机制,可以实现很多原来必须通过下标才能写出的功能,这让代码变得更加清晰,而且能充分发挥Python的优势。
要点
- unpacking是一种特殊的Python语法,只需要一行代码,就能把数据结构里面的多个值分别赋给相应的变量。
- unpacking在Python中应用广泛,凡是可迭代的对象都能拆分,无论它里面还有多少层迭代结构。
- 尽量通过unpacking来拆解序列之中的数据,而不要通过下标访问,这样可以让代码更简洁、更清晰。