Python列表的查找、排序和反转是脚本中高频出现的一组操作,但几个方法的行为差异很容易踩坑:index找不到会抛ValueError,sort和reverse原地修改且返回None,reversed返回的迭代器只能消费一次,切片[::-1]则会新建列表。下面围绕这些方法重组原文代码与实践,重点说明参数、返回值和选择场景。
一、查找:index与count
index(x)返回第一次出现索引;index(x,start)从start开始;index(x,start,end)在左闭右开区间查找。不存在抛ValueError,生产代码应包装安全查找。- fruits = ['苹果', '香蕉', '橘子', '葡萄', '香蕉', '西瓜']
- print(fruits.index('香蕉'))
- print(fruits.index('香蕉', 2))
- print(fruits.index('香蕉', 1, 3))
- try:
- fruits.index('柠檬')
- except ValueError as e:
- print('错误:', e)
复制代码 安全查找有三种常见写法:try/except包装、先用in判断但会遍历两次、用enumerate支持条件查找。查找所有匹配位置可用列表推导。- def safe_index(lst, value, default=-1):
- try:
- return lst.index(value)
- except ValueError:
- return default
- def find_first(lst, predicate):
- for i, item in enumerate(lst):
- if predicate(item):
- return i, item
- return -1, None
- def find_all(lst, value):
- return [i for i, x in enumerate(lst) if x == value]
- nums = [3, 7, 2, 9, 4, 6]
- print(find_first(nums, lambda x: x > 5))
- data = [1, 3, 5, 3, 7, 3, 9]
- print(find_all(data, 3))
复制代码 count(x)统计出现次数,不存在的元素返回0而不报错。求众数可用max(set(lst), key=lst.count);重复检查可用len(lst)!=len(set(lst));频率统计用Counter和most_common。- from collections import Counter
- nums = [1, 2, 3, 2, 1, 2, 3, 2, 1, 2]
- print(nums.count(2))
- print(nums.count(999))
- print(max(set(nums), key=nums.count))
- print(len(nums) != len(set(nums)))
- counter = Counter(nums)
- print(counter)
- print(counter.most_common(2))
复制代码
二、排序:sort原地与sorted新列表
sort()原地排序,返回None;reverse=True降序;key指定排序键。不要把sort返回值赋给变量,也不要放在链式调用中。- nums = [5, 2, 8, 1, 9, 3]
- nums.sort()
- print(nums)
- nums.sort(reverse=True)
- print(nums)
- words = ['banana', 'apple', 'cherry', 'date']
- words.sort(key=len)
- print(words)
- result = nums.sort()
- print(result)
复制代码 sorted()返回新列表,不修改原对象,可用于字符串、集合、元组、生成器等任意可迭代对象。- nums = [5, 2, 8, 1, 9, 3]
- sorted_nums = sorted(nums)
- print(nums)
- print(sorted_nums)
- print(sorted('python'))
- print(sorted({3, 1, 4, 1, 5}))
- print(sorted((3, 1, 4, 2)))
- gen = (x ** 2 for x in range(10, 0, -1))
- print(sorted(gen))
复制代码 选择规则:需要保留原列表用sorted;只需结果且可改原列表用sort省内存;非列表可迭代对象只能sorted;链式表达式用sorted。
三、key参数与operator
key接收一个函数,排序时用该函数的返回值比较,而不是直接比较元素本身。常见用法包括len、abs、str.lower、lambda取字段。字典列表可按字段排序;元组key可实现多级排序,如(-成绩,班级);条件排序如(x%2,x)可把偶数放前、奇数放后。- words = ['a', 'abcde', 'abc', 'ab', 'abcd']
- words.sort(key=len)
- print(words)
- nums = [-5, 3, -1, 4, -2]
- nums.sort(key=abs)
- print(nums)
- users = [
- {'name': '小明', 'age': 25},
- {'name': '小红', 'age': 23},
- {'name': '小刚', 'age': 26},
- ]
- users.sort(key=lambda u: u['age'])
- print(users)
- students = [
- ('小明', 85, 2),
- ('小红', 92, 1),
- ('小刚', 85, 1),
- ('小李', 92, 2),
- ]
- students.sort(key=lambda s: (-s[1], s[2]))
- print(students)
- nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
- nums.sort(key=lambda x: (x % 2, x))
- print(nums)
复制代码 简单字段访问优先用itemgetter或attrgetter,它们在C层面实现,通常比lambda更快;methodcaller可按方法调用结果排序。- from operator import itemgetter, attrgetter, methodcaller
- students = [
- ('小明', 85, 'A班'),
- ('小红', 92, 'B班'),
- ('小刚', 78, 'A班'),
- ]
- students.sort(key=itemgetter(1))
- print(students)
- students.sort(key=itemgetter(2, 1))
- print(students)
- class Student:
- def __init__(self, name, score):
- self.name = name
- self.score = score
- def __repr__(self):
- return f'Student({self.name}, {self.score})'
- students = [Student('小明', 85), Student('小红', 92), Student('小刚', 78)]
- students.sort(key=attrgetter('score'))
- print(students)
- words = ['hello', 'World', 'PYTHON', 'Code']
- words.sort(key=methodcaller('lower'))
- print(words)
复制代码 Python排序是稳定的,相等元素的相对顺序在排序后保持不变。利用稳定性可以从次要条件开始排,再排主要条件;也可以用元组key一次完成。原文示例先按成绩降序再按班级升序,结果与(x[1],-x[2])元组key一致。- data = [
- ('小明', 'A班', 85),
- ('小红', 'B班', 92),
- ('小刚', 'A班', 78),
- ('小李', 'B班', 88),
- ('小王', 'A班', 95),
- ]
- data.sort(key=lambda x: x[2], reverse=True)
- data.sort(key=lambda x: x[1])
- print(data)
- data2 = data[:]
- data2.sort(key=lambda x: (x[1], -x[2]))
- print(data2)
复制代码
四、反转:reverse、reversed与[::-1]
reverse()原地反转列表并返回None,额外内存为O(1);reversed()返回反向迭代器,不修改原列表,迭代器耗尽后再转列表会为空;[::-1]创建反转后的新列表,额外内存为O(n)。选择建议:需要修改原列表用reverse;只需要反向遍历用reversed;需要保留原列表且要新列表用[::-1]或list(reversed(lst))。- nums = [1, 2, 3, 4, 5]
- nums.reverse()
- print(nums)
- nums = [1, 2, 3, 4, 5]
- rev = reversed(nums)
- print(list(rev))
- print(list(rev))
- print(nums)
- for num in reversed(nums):
- print(num, end=' ')
- print()
- new_list = nums[::-1]
- print(new_list)
- print(nums)
复制代码
五、实战:排行榜与优先级任务队列
排行榜维护(name,score)列表,每次添加后按分数降序sort;get_rank用enumerate找名次;get_top_n用切片取前N;get_around_player按排名截取周边玩家。该类把排序、查找和切片组合在一起,适合脚本中的简单排名场景。- class Leaderboard:
- def __init__(self):
- self._entries = []
- def add_score(self, name, score):
- self._entries.append((name, score))
- self._entries.sort(key=lambda x: x[1], reverse=True)
- def get_rank(self, name):
- for i, (n, s) in enumerate(self._entries):
- if n == name:
- return i + 1
- return -1
- def get_top_n(self, n):
- return self._entries[:n]
- def get_around_player(self, name, radius=2):
- rank = self.get_rank(name)
- if rank == -1:
- return []
- idx = rank - 1
- start = max(0, idx - radius)
- end = min(len(self._entries), idx + radius + 1)
- return self._entries[start:end]
- lb = Leaderboard()
- lb.add_score('小明', 850)
- lb.add_score('小红', 920)
- lb.add_score('小刚', 780)
- lb.add_score('小李', 950)
- lb.add_score('小王', 880)
- lb.add_score('小张', 830)
- print(lb.get_rank('小李'))
- print(lb.get_top_n(3))
- print(lb.get_around_player('小刚', 1))
复制代码 优先级任务队列用sort加key排序:priority小的优先,同优先级按deadline排序;get_next_task排序后pop(0);find_tasks_by_priority用列表推导过滤。- class PriorityTaskQueue:
- def __init__(self):
- self._tasks = []
- def add_task(self, name, priority, deadline):
- self._tasks.append({
- 'name': name,
- 'priority': priority,
- 'deadline': deadline,
- })
- def get_next_task(self):
- if not self._tasks:
- return None
- self._tasks.sort(key=lambda t: (t['priority'], t['deadline']))
- return self._tasks.pop(0)
- def find_tasks_by_priority(self, priority):
- return [t for t in self._tasks if t['priority'] == priority]
- def pending_count(self):
- return len(self._tasks)
- queue = PriorityTaskQueue()
- queue.add_task('发送邮件', 2, 1000)
- queue.add_task('数据库备份', 1, 900)
- queue.add_task('生成报表', 2, 850)
- queue.add_task('清理日志', 3, 1200)
- while queue.pending_count() > 0:
- task = queue.get_next_task()
- print('处理:', task['name'], '优先级:', task['priority'])
复制代码
六、性能与最佳实践
第一,牢记返回值:sort和reverse返回None,index找不到抛ValueError,count找不到返回0,reversed返回一次性迭代器。第二,按内存与数据安全选方法:sort和reverse原地修改节省内存,但会改变原数据;sorted和[::-1]创建新列表,适合保留原列表;reversed只做惰性遍历。第三,简单字段排序优先operator模块的itemgetter、attrgetter,减少lambda调用开销。第四,多级排序可用稳定排序分步完成,也可用元组key一次表达,注意降序字段用负号或reverse参数。第五,查找复杂条件时用enumerate比反复index更直接,统计频率时Counter比多次count更清晰。原文还给出商品价格排序与过滤的函数签名和部分过滤思路,包括先浅拷贝、按价格区间过滤、关键词搜索、分类过滤,再用sort_keys映射排序;但该段代码在原文中不完整,这里不补写未给出的实现。
整体来看,列表查找、排序和反转的关键不在记方法名,而在明确每个方法的返回值、是否修改原对象、额外内存以及适用迭代对象。把这些边界条件写进脚本,才能避免index异常、sort返回值误用和reversed迭代器耗尽等常见问题。 |