高效Python90条之第6条 把数据结构直接拆分到多个变量里,不要专门通过下标访问 ¶
Python内置的元组(tuple)类型可以创建不可变的序列,把许多元素依次保存起来。 可以用整数作下标,通过下标来访问元组里面对应的元素。但不能通过下标给元素赋新值。
snack_calories = {"chips": 140, "popcorn": 80, "nuts": 190}
items = tuple(snack_calories.items())
print(type(snack_calories)) # <class 'dict'>
print(type(items)) # <class 'tuple'>
print(snack_calories) # {'chips': 140, 'popcorn': 80, 'nuts': 190}
print(items) # (('chips', 140), ('popcorn', 80), ('nuts', 190))
print(items[0]) # ('chips', 140)
print(items[1]) # ('popcorn', 80)
print(items[2]) # ('nuts', 190)
items[1] = ("apple", 200)
# TypeError: 'tuple' object does not support item assignment
Python还有一种写法,叫作拆分(unpacking)。这种写法让我们只用一条语句,就可以把元组里面的元素分别赋给多个变量,不用再通过下标去访问。 元组的元素本身不能修改,但是那些被赋值的变量是可以修改的。 通过unpacking来赋值要比通过下标去访问元组内的元素更清晰,而且这种写法所需的代码量通常比较少。当然,赋值操作的左边除了可以罗列单个变量,也可以写成列表、序列或任意深度的可迭代对象(iterable)。
favorite_snacks = {
"salty": ("pretzels", 100),
"sweet": ("cookies", 280),
"veggie": ("carrots", 20),
}
(
(type1, (name1, cals1)),
(type2, (name2, cals2)),
(type3, (name3, cals3)),
) = favorite_snacks.items()
print(f"Favorite {type1} is {name1} with {cals1} calories")
# Favorite salty is pretzels with 100 calories
print(f"Favorite {type2} is {name2} with {cals2} calories")
# Favorite sweet is cookies with 280 calories
print(f"Favorite {type3} is {name3} with {cals3} calories")
# Favorite veggie is carrots with 20 calories
可以通过unpacking原地交换两个变量,而不用专门创建临时变量。
# 传统方法
def bubble_sort(a):
for _ in range(len(a)):
for i in range(1, len(a)):
if a[i] < a[i - 1]:
temp = a[i]
a[i] = a[i - 1]
a[i - 1] = temp
names = ["pretzels", "carrots", "arugula", "bacon"]
bubble_sort(names)
print(names)
# ['arugula', 'bacon', 'carrots', 'pretzels']
# Unpacking方法
def bubble_sort(a):
for _ in range(len(a)):
for i in range(1, len(a)):
if a[i] < a[i - 1]:
a[i], a[i - 1] = a[i - 1], a[i]
names = ["pretzels", "carrots", "arugula", "bacon"]
bubble_sort(names)
print(names)
# ['arugula', 'bacon', 'carrots', 'pretzels']
原理分析:
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循环或者类似的结构里面,把复杂的数据拆分到相关的变量之中。
snacks = [("bacon", 350), ("donut", 240), ("muffin", 190)]
for i in range(len(snacks)):
item = snacks[i]
name = item[0]
calories = item[1]
print(f"#{i+1}: {name} has {calories} calories")
#1: bacon has 350 calories
#2: donut has 240 calories
#3: muffin has 190 calories
上面这段代码虽然没错,但看起来很乱,因为snacks
结构本身并不是一份简单的列表,它的每个元素都是一个元组, 所以必须逐层访问才能查到最为具体的数据,也就是每种零食的名称(name)及卡路里(calories)。
下面换一种写法,首先调用内置的enumerate
函数(参见第7条)获得当前要迭代的元组, 然后针对这个元组做unpacking,这样就可以直接得到具体的name
与calories
值了。
这才是符合Python风格的写法(Pythonic式的写法)。
snacks = [("bacon", 350), ("donut", 240), ("muffin", 190)]
for rank, (name, calories) in enumerate(snacks, 1):
print(f'#{rank}: {name} has {calories} calories')
#1: bacon has 350 calories
#2: donut has 240 calories
#3: muffin has 190 calories
Python的unpacking机制可以用在许多方面,例如构建列表(Rule13)、给函数设计参数列表(Rule22)、传递关键字参数(Rule23)、接收多个返回值(Rule19条)等。
要点:
- unpacking是一种特殊的Python语法,只需要一行代码,就能把数据结构里面的多个值分别赋给相应的变量。
- unpacking在Python中应用广泛,凡是可迭代的对象都能拆分,无论它里面还有多少层迭代结构。
- 尽量通过unpacking来拆解序列之中的数据,而不要通过下标访问,这样可以让代码更简洁、更清晰。
拓展:enumerate ¶
enumerate()
函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
enumerate()
方法:
语法:
enumerate(sequence, [start=0])
参数:
sequence
:一个序列、迭代器或其他支持迭代对象。start
:下标起始位置。 返回值
返回:
enumerate(枚举)
对象。
基本用法:
- 字符串
sample = "abcd"
for i, j in enumerate(sample): # 输出的是元组内的元素
print(i, j)
# 0 a
# 1 b
# 2 c
# 3 d
for i in enumerate(sample): # 输出的是元组
print(i)
# (0, 'a')
# (1, 'b')
# (2, 'c')
# (3, 'd')
- 元组
sample = ("abcd", "hijk")
for i, j in enumerate(sample):
print(i, j)
# 0 abcd
# 1 hijk
sample = ("abcd", "hijk")
for i, j in enumerate(sample, 2):
print(i, j)
# 2 abcd
# 3 hijk
- 数组
sample = ["abcd", "hijk"]
for i, j in enumerate(sample):
print(i, j)
# 0 abcd
# 1 hijk
- 字典
sample = {"abcd": 1, "hijk": 2}
for i, j in enumerate(sample):
print(i, j)
# 0 abcd
# 1 hijk