a = [[1,2,3],[3,5,1],[6,4,2]] # List of List
a[0] [1, 2, 3]
在前面的学习中,我们已经学习了Python的基本数据结构,如列表、元组和字典,了解了它们的创建、元素访问、修改和遍历等基础操作,也接触了列表推导式和字典推导式。
本章在这些内容的基础上,补充一些常用的数据结构操作和相关内置函数。主要内容包括:
key参数和lambda函数指定排序依据。enumerate、zip和pop等常用操作及其应用场景。这些内容有助于后续处理列表、字典和简单表格型数据。
本章内容较多,可以按两个层次学习。
必修主线:嵌套结构、排序和配对遍历
sorted() 和 .sort() 的区别reverse=Truekey= 参数:按嵌套结构中的某个字段排序keylambda 作为 key.items()key 返回元组enumerate():遍历时同时获得索引和值zip():按位置配对多个序列dict(zip(keys, values))zip()zip(*data) 拆开成列pop():从 List 或 Dict 中取出并删除元素进阶学习:排序工具、长度不齐的配对和队列操作
operator.itemgetterenumerate(..., start=1)itertools.zip_longestcollections.dequedeque.popleft()list.pop(0) 的性能问题数据结构可以相互嵌套。比如List嵌套List,后面我们会知道这是表示矩阵的一种方式。
a = [[1,2,3],[3,5,1],[6,4,2]] # List of List
a[0] [1, 2, 3]
列表嵌套元组:
b = [('A',2),('B',3),('C',1)]
b[2]('C', 1)
字典的值为列表或元组:
c = {'A':[1,2,3],'B':(3,4,5),'C':[2,3,4]}
c['B'](3, 4, 5)
嵌套也可以有多层。
嵌套的数据结构,其取值的方法也和原来一样,按照索引或者key逐级排列即可。
a[0][1]2
b[1][0]'B'
c['B'][1]4
列表常用的排序方式有两种。
sorted()函数返回一个排序后的新列表,原列表不变。.sort()方法,对原列表进行“原地排序”,原列表会改变,并且方法本身返回 None。a = [3,2,1,5]print(sorted(a)) #返回排序后的新列表
print(a) # 注意sorted函数不会改变a[1, 2, 3, 5]
[3, 2, 1, 5]
a.sort() # 原地排序,会改变a
print(a)[1, 2, 3, 5]
注意:.sort() 会修改原列表,并返回 None。因此不要写成 b = a.sort() 来保存排序结果。如果需要新列表,应使用 b = sorted(a)。
逆序的话,需要加入reverse=True参数。
a = [2,5,1,3]
print(sorted(a, reverse=True)) # 逆序,sorted返回一个排序好的新列表,不改变原值
print(a) # a保持不变
a.sort(reverse=True) # 逆序,原地排序,这个方法不返回值。
print(a) # a已经被改变[5, 3, 2, 1]
[2, 5, 1, 3]
[5, 3, 2, 1]
对于嵌套数据结构和字典等等,我们可以构造任意的排序规则。例如,一个嵌套的List:
a = [['C',1,2],['A',3,1],['B',2,6]]
a[['C', 1, 2], ['A', 3, 1], ['B', 2, 6]]
我们想根据每个子List的第二个元素来排序,需要用到参数key(上面两种排序方法都适用)
key参数接受一个函数换句话说,你给出一个排序的凭据,要构造一个比较不同子List大小办法,这个办法的结果,体现在你要算出一个可比较的值,如一个数字。
落实到本例,如果只是比较第二个元素的大小,则直接返回第二个元素即可。
注意:函数可以作为参数传递给另一个函数。这里 sorted() 会对列表 a 的每个元素调用一次 get_2nd_item(),并根据这个函数返回的值排序。
def get_2nd_item(x):
return x[1]
# get_2nd_item 是一个函数。这里把它传递给 sorted,作为排序依据。
sorted(a, key=get_2nd_item)[['C', 1, 2], ['B', 2, 6], ['A', 3, 1]]
也可以采用匿名函数,见前面的章节。
# 写成匿名函数的版本
sorted(a, key=lambda x: x[1]) [['C', 1, 2], ['B', 2, 6], ['A', 3, 1]]
例如,如果要根据“第2和第3个元素的和”来排序,只需要构造一个函数,返回这两者的和即可。
def sum_2_3(x):
return x[1] + x[2]
print(a[0]) # 测试一下是否正常
sorted(a, key=sum_2_3)['C', 1, 2]
[['C', 1, 2], ['A', 3, 1], ['B', 2, 6]]
# 写成匿名函数的版本
sorted(a, key=lambda x: x[1] + x[2])[['C', 1, 2], ['A', 3, 1], ['B', 2, 6]]
前面说过,字典大约也可以“看成”一个嵌套数据结构,以前面的字典c为例
c.items()dict_items([('A', [1, 2, 3]), ('B', (3, 4, 5)), ('C', [2, 3, 4])])
注意到value是一个3个元素的List或者Tuple,我们要根据value的第一个元素来排序。
sorted_items = sorted(c.items(), key=lambda x: x[1][0])
sorted_items[('A', [1, 2, 3]), ('C', [2, 3, 4]), ('B', (3, 4, 5))]
注意,其中x是一个字典中的item,即一个(key,value)对。那么x[0]就是key,x[1]就是value,x[1][0]就是value中的第一个元素了。
注意上面得到的是一个List of Tuple,即单纯的嵌套数据结构。如果需要转为字典,调用dict()函数即可(简单类型转换)
dict(sorted(c.items(), key=lambda x: x[1][0])){'A': [1, 2, 3], 'C': [2, 3, 4], 'B': (3, 4, 5)}
注意:排序本质上是对 dict.items() 得到的 (key, value) 序列排序。字典本身不是专门用于排序的数据结构,只是 Python 3.7 及更高版本会保留元素的插入顺序。因此,把排序后的 items() 转回字典后,可以看到排序后的顺序。本课程环境使用的 Python 版本支持这一点。
最后,也可以用key参数实现多级排序。lambda 函数可以返回一个元组,Python 在比较元组时,会按元组中元素的顺序逐个比较。
例如:按成绩降序,成绩相同再按年龄升序。
data = [{'name': 'Alice', 'score': 85, 'age': 20},
{'name': 'Bob', 'score': 92, 'age': 21},
{'name': 'Clare', 'score': 85, 'age': 19}]
# 按分数降序,如果分数相同,则按年龄升序
sorted_data = sorted(data, key=lambda x: (-x['score'], x['age']))
# 注意:分数取负数来实现降序,年龄正常是升序
print(sorted_data)[{'name': 'Bob', 'score': 92, 'age': 21}, {'name': 'Clare', 'score': 85, 'age': 19}, {'name': 'Alice', 'score': 85, 'age': 20}]
注意:同样为85分,Clare 年龄小,排在了 Alice 前面。
enumerate()函数一般用于遍历一个可迭代对象的时候,同时还可以获得对应的索引。
或者说,你遍历一个a_list = [‘a’,‘b’,‘c’]的时候,还想获得当前值是“第几个”,我们用以下循环
for i,value in enumerate(a_list):
a_list = list('apple')
for i,value in enumerate(a_list):
print(i,value) # 此时,i就表示遍历过程中的索引0 a
1 p
2 p
3 l
4 e
和字典.items()类似,enumerate(a_list)可以得到形如 [(index, value), (index, value), ...] 的序列。索引默认从 0 开始。
注:enumerate(a_list)返回一个 enumerate 对象(迭代器),当你访问其中的值时才会逐个产生;如果要打印出来看,需要转为 list,也可以转为字典。
# 把a_list,转为带有索引的list
print(list(enumerate(a_list)))
# 把a_list,转为以index为key的字典
print(dict(enumerate(a_list)))[(0, 'a'), (1, 'p'), (2, 'p'), (3, 'l'), (4, 'e')]
{0: 'a', 1: 'p', 2: 'p', 3: 'l', 4: 'e'}
进一步,可以获得“某个元素的索引”,比如问’p’是几号元素?
a_list = list('apple')
[i for i,value in enumerate(a_list) if value == 'p'][1, 2]
把几个序列结构,每个元素对位组合,形成一个Tuple,再组成一个List。
注意:zip() 会按最短序列停止,较长序列后面的元素会被丢弃。
list1= ('a', 'b', 'c')
list2 = [1, 2, 3, 4]
print(list(zip(list1, list2)))
print(list(zip(list1, list2, list1))) # 可以组合不止2个序列[('a', 1), ('b', 2), ('c', 3)]
[('a', 1, 'a'), ('b', 2, 'b'), ('c', 3, 'c')]
这类二元组序列可以直接转换为字典。
dict(zip(list1, list2)){'a': 1, 'b': 2, 'c': 3}
也可以同时遍历多个序列。
for i, j in zip(list1, list2):
print(i,j)a 1
b 2
c 3
回忆函数的拆包操作,我把一个List(等)传递给一个函数,前面加一个*,就会自动把List中的元素逐一拆出来,传递给函数。
def add(a,b):
return a + b
add(*[1,2]) # 对[1,2]进行拆包:按顺序传递给a和b3
对于zip函数也可以这样做,比如你有一个序列
a_list = [(1, 'one'), (2, 'two'), (3, 'three')]
如果希望把第一位的元素组成一个序列、第二位的元素组成另一个序列,可以使用 zip(*a_list)。这相当于把按行组织的数据拆成按列组织的数据。
a_list = [(1, 'one'), (2, 'two'), (3, 'three')]
list(zip(*a_list))[(1, 2, 3), ('one', 'two', 'three')]
x, y = list(zip(*a_list)) # 如果要赋值的话,可以这么写pop() 方法pop()方法用于“返回并移除一个元素”。对列表来说,可以理解为把一个元素取出来,同时从列表中删除。
对于List,默认是最后一个元素(队尾)。
a = list('apple')
print(a.pop()) # 获取最后一个元素,并删除。
print(a) # 最后一个元素已经删除e
['a', 'p', 'p', 'l']
也可以指定index,比如拿出1号元素。
print(a.pop(1))
print(a)p
['a', 'p', 'l']
对于 Dict,需要指定 key;pop(key[, default]) 会返回对应的 value,并移除该键;提供 default 可避免 KeyError。
b = {'a': 1, 'b': 2, 'c': 3}
print(b.pop('b')) # 获取'b'对应的value,并删除'b'。
print(b) # 'b'已经删除2
{'a': 1, 'c': 3}
以下是同学的姓名、学号和考试分数的数据:
names = ["Alice", "Bob", "Clare"]
ids = [101, 102, 103]
scores = [85, 92, 78]
data,其中 key 是名字,value 是另一个字典,包含 id 和 score。目标结构大致如下:
{
'Alice': {'id': 101, 'score': 85},
'Bob': {'id': 102, 'score': 92},
'Clare': {'id': 103, 'score': 78}
}
data 按分数排序,高分在前。排序后的姓名顺序应为:
['Bob', 'Alice', 'Clare']
分数最高的同学是: Bob,学号是102,分数是:92。
key 的补充用法operator.itemgetter/attrgetter 提升可读性。reverse=True 作用于整体;若需混合方向,可用“带符号的元组 key”或利用稳定排序分两步。from operator import itemgetter
a = [['C', 1, 2], ['A', 3, 1], ['B', 2, 6]]
print(sorted(a, key=itemgetter(1))) # 等价于 key=lambda x: x[1]
data = [
{'name': 'Alice', 'score': 85, 'age': 20},
{'name': 'Bob', 'score': 92, 'age': 21},
{'name': 'Clare', 'score': 85, 'age': 19},
]
# 多键(均升序)
print(sorted(data, key=itemgetter('score', 'age')))
# 混合方向:先按 age 升序稳定排序,再按 score 降序
tmp = sorted(data, key=itemgetter('age'))
print(sorted(tmp, key=itemgetter('score'), reverse=True))[['C', 1, 2], ['B', 2, 6], ['A', 3, 1]]
[{'name': 'Clare', 'score': 85, 'age': 19}, {'name': 'Alice', 'score': 85, 'age': 20}, {'name': 'Bob', 'score': 92, 'age': 21}]
[{'name': 'Bob', 'score': 92, 'age': 21}, {'name': 'Clare', 'score': 85, 'age': 19}, {'name': 'Alice', 'score': 85, 'age': 20}]
通过 start= 参数可以让索引从 1 开始计数。
a_list = ['a', 'b', 'c']
for idx, val in enumerate(a_list, start=1):
print(idx, val)1 a
2 b
3 c
当序列长度不一时,zip 按最短截断;如需保留最长,可用 itertools.zip_longest。
from itertools import zip_longest
list1 = ('a', 'b', 'c')
list2 = [1, 2, 3, 4]
print(list(zip_longest(list1, list2, fillvalue=None)))[('a', 1), ('b', 2), ('c', 3), (None, 4)]
list.pop(0) 为 O(n);频繁从头部出队建议用 collections.deque(popleft() 为 O(1))。
from collections import deque
dq = deque([1, 2, 3])
dq.popleft()
print(dq)deque([2, 3])
enumerate 是迭代器:被消费后需重新构造;打印需 list(...)。zip 截断:长度不齐会丢后续元素;需要保留用 itertools.zip_longest。sorted vs .sort():sorted() 返回新列表;.sort() 原地修改并返回 None。dict.pop(key):key 不存在会抛 KeyError,可用 pop(key, default)。list.pop(i):索引越界抛 IndexError;pop(0) 性能差,队列用 deque.popleft()。reverse=True 作用于整体;需“分字段正/逆序”时,用带符号的元组 key 或稳定排序两步法。x[1][0] 是否匹配实际结构。reverse=True 或映射策略。本章节主要补充了Python数据结构的一些常用操作。我们学习了:
sorted()函数和列表自身的.sort()方法,并重点掌握了通过key参数和lambda函数来定义复杂排序逻辑,例如根据嵌套列表或元组中的特定元素进行排序。dict()转换(注意Python 3.7+版本字典保持插入顺序的特性)。enumerate()函数,它可以在遍历序列时同时返回元素的索引和值。zip()函数,它可以将多个可迭代对象中对应位置的元素打包成一个个元组。pop()方法,它可以从列表(按索引)或字典(按键)中移除一个元素并返回该元素的值。这些内容在数据处理和算法实现中经常使用。建议通过课后练习,把这些知识点用于具体的数据处理场景。