第6条 把数据结构直接拆分到多个变量里,不要专门通过下标访问
Python内置的元组(tuple
)类型可以创建不可变的序列,把许多元素依次保存起来。最简单的用法是只用元组保存两个值,例如字典里面的键值对。
我们可以用整数作下标,通过下标来访问元组里面对应的元素。
创建好tuple
之后,就不能通过下标给其中的元素赋新值了。
Python还有一种写法,叫作拆分(unpacking)。这种写法让我们只用一条语句,就可以把元组里面的元素分别赋给多个变量。对元组做unpacking的时候,有人觉得,这好像是要修改它里面的元素,其实并不是这样。元组的元素本身不能修改,但是可以通过unpacking把这些元素分别赋给相应的变量,那些变量是可以修改的。例如,若我们确定某个元组里面只有两个元素,那么就可以直接把这两个元素分别赋给相应的变量,而不用再通过下标去访问。
通过unpacking来赋值要比通过下标去访问元组内的元素更清晰,而且这种写法所需的代码量通常比较少。当然,赋值操作的左边除了可以罗列单个变量,也可以写成列表、序列或任意深度的可迭代对象(iterable)。例如,下面这种写法就是成立的,然而笔者并不推荐大家在代码里这样写,举这个例子只是想让大家了解Python支持这样的写法,以及这种写法为什么可以成立。
Python新手可能还不知道,其实我们可以通过unpacking原地交换两个变量,而不用专门创建临时变量。先来看下面这种传统的写法,这个升序排序算法用下标的形式来访问有待排序的这个a
列表,并且通过temp
变量交换其中两个相邻的元素。
有了unpacking机制之后,只需要写一行代码就可以交换这两个元素,而不用像刚才那样分成三行来写。
这样写为什么可以成立呢?因为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
列表里面的元素。
这样写虽然没错,但看起来很乱,因为snacks
结构本身并不是一份简单的列表,它的每个元素都是一个元组,所以必须逐层访问才能查到最为具体的数据,也就是每种零食的名称(name
)及卡路里(calories
)。下面换一种写法,首先调用内置的enumerate
函数(参见第7条)获得当前要迭代的元组,然后针对这个元组做unpacking,这样就可以直接得到具体的name
与calories
值了。
这才是符合Python风格的写法(Pythonic式的写法),我们不需要再通过下标逐层访问了。这种写法可以节省篇幅,而且比较容易理解。
Python的unpacking机制可以用在许多方面,例如构建列表(参见第13条)、给函数设计参数列表(参见第22条)、传递关键字参数(参见第23条)、接收多个返回值(参见第19条)等。
明智地使用unpacking机制,可以实现很多原来必须通过下标才能写出的功能,这让代码变得更加清晰,而且能充分发挥Python的优势。
要点
- unpacking是一种特殊的Python语法,只需要一行代码,就能把数据结构里面的多个值分别赋给相应的变量。
- unpacking在Python中应用广泛,凡是可迭代的对象都能拆分,无论它里面还有多少层迭代结构。
- 尽量通过unpacking来拆解序列之中的数据,而不要通过下标访问,这样可以让代码更简洁、更清晰。