查看: 201|回复: 3

Python列表查找排序反转:index与sort区别

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Python列表的查找、排序和反转是脚本中高频出现的一组操作,但几个方法的行为差异很容易踩坑:index找不到会抛ValueError,sort和reverse原地修改且返回None,reversed返回的迭代器只能消费一次,切片[::-1]则会新建列表。下面围绕这些方法重组原文代码与实践,重点说明参数、返回值和选择场景。

一、查找:index与count
index(x)返回第一次出现索引;index(x,start)从start开始;index(x,start,end)在左闭右开区间查找。不存在抛ValueError,生产代码应包装安全查找。
  1. fruits = ['苹果', '香蕉', '橘子', '葡萄', '香蕉', '西瓜']
  2. print(fruits.index('香蕉'))
  3. print(fruits.index('香蕉', 2))
  4. print(fruits.index('香蕉', 1, 3))
  5. try:
  6.     fruits.index('柠檬')
  7. except ValueError as e:
  8.     print('错误:', e)
复制代码
安全查找有三种常见写法:try/except包装、先用in判断但会遍历两次、用enumerate支持条件查找。查找所有匹配位置可用列表推导。
  1. def safe_index(lst, value, default=-1):
  2.     try:
  3.         return lst.index(value)
  4.     except ValueError:
  5.         return default
  6. def find_first(lst, predicate):
  7.     for i, item in enumerate(lst):
  8.         if predicate(item):
  9.             return i, item
  10.     return -1, None
  11. def find_all(lst, value):
  12.     return [i for i, x in enumerate(lst) if x == value]
  13. nums = [3, 7, 2, 9, 4, 6]
  14. print(find_first(nums, lambda x: x > 5))
  15. data = [1, 3, 5, 3, 7, 3, 9]
  16. print(find_all(data, 3))
复制代码
count(x)统计出现次数,不存在的元素返回0而不报错。求众数可用max(set(lst), key=lst.count);重复检查可用len(lst)!=len(set(lst));频率统计用Counter和most_common。
  1. from collections import Counter
  2. nums = [1, 2, 3, 2, 1, 2, 3, 2, 1, 2]
  3. print(nums.count(2))
  4. print(nums.count(999))
  5. print(max(set(nums), key=nums.count))
  6. print(len(nums) != len(set(nums)))
  7. counter = Counter(nums)
  8. print(counter)
  9. print(counter.most_common(2))
复制代码

二、排序:sort原地与sorted新列表
sort()原地排序,返回None;reverse=True降序;key指定排序键。不要把sort返回值赋给变量,也不要放在链式调用中。
  1. nums = [5, 2, 8, 1, 9, 3]
  2. nums.sort()
  3. print(nums)
  4. nums.sort(reverse=True)
  5. print(nums)
  6. words = ['banana', 'apple', 'cherry', 'date']
  7. words.sort(key=len)
  8. print(words)
  9. result = nums.sort()
  10. print(result)
复制代码
sorted()返回新列表,不修改原对象,可用于字符串、集合、元组、生成器等任意可迭代对象。
  1. nums = [5, 2, 8, 1, 9, 3]
  2. sorted_nums = sorted(nums)
  3. print(nums)
  4. print(sorted_nums)
  5. print(sorted('python'))
  6. print(sorted({3, 1, 4, 1, 5}))
  7. print(sorted((3, 1, 4, 2)))
  8. gen = (x ** 2 for x in range(10, 0, -1))
  9. print(sorted(gen))
复制代码
选择规则:需要保留原列表用sorted;只需结果且可改原列表用sort省内存;非列表可迭代对象只能sorted;链式表达式用sorted。

三、key参数与operator
key接收一个函数,排序时用该函数的返回值比较,而不是直接比较元素本身。常见用法包括len、abs、str.lower、lambda取字段。字典列表可按字段排序;元组key可实现多级排序,如(-成绩,班级);条件排序如(x%2,x)可把偶数放前、奇数放后。
  1. words = ['a', 'abcde', 'abc', 'ab', 'abcd']
  2. words.sort(key=len)
  3. print(words)
  4. nums = [-5, 3, -1, 4, -2]
  5. nums.sort(key=abs)
  6. print(nums)
  7. users = [
  8.     {'name': '小明', 'age': 25},
  9.     {'name': '小红', 'age': 23},
  10.     {'name': '小刚', 'age': 26},
  11. ]
  12. users.sort(key=lambda u: u['age'])
  13. print(users)
  14. students = [
  15.     ('小明', 85, 2),
  16.     ('小红', 92, 1),
  17.     ('小刚', 85, 1),
  18.     ('小李', 92, 2),
  19. ]
  20. students.sort(key=lambda s: (-s[1], s[2]))
  21. print(students)
  22. nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  23. nums.sort(key=lambda x: (x % 2, x))
  24. print(nums)
复制代码
简单字段访问优先用itemgetter或attrgetter,它们在C层面实现,通常比lambda更快;methodcaller可按方法调用结果排序。
  1. from operator import itemgetter, attrgetter, methodcaller
  2. students = [
  3.     ('小明', 85, 'A班'),
  4.     ('小红', 92, 'B班'),
  5.     ('小刚', 78, 'A班'),
  6. ]
  7. students.sort(key=itemgetter(1))
  8. print(students)
  9. students.sort(key=itemgetter(2, 1))
  10. print(students)
  11. class Student:
  12.     def __init__(self, name, score):
  13.         self.name = name
  14.         self.score = score
  15.     def __repr__(self):
  16.         return f'Student({self.name}, {self.score})'
  17. students = [Student('小明', 85), Student('小红', 92), Student('小刚', 78)]
  18. students.sort(key=attrgetter('score'))
  19. print(students)
  20. words = ['hello', 'World', 'PYTHON', 'Code']
  21. words.sort(key=methodcaller('lower'))
  22. print(words)
复制代码
Python排序是稳定的,相等元素的相对顺序在排序后保持不变。利用稳定性可以从次要条件开始排,再排主要条件;也可以用元组key一次完成。原文示例先按成绩降序再按班级升序,结果与(x[1],-x[2])元组key一致。
  1. data = [
  2.     ('小明', 'A班', 85),
  3.     ('小红', 'B班', 92),
  4.     ('小刚', 'A班', 78),
  5.     ('小李', 'B班', 88),
  6.     ('小王', 'A班', 95),
  7. ]
  8. data.sort(key=lambda x: x[2], reverse=True)
  9. data.sort(key=lambda x: x[1])
  10. print(data)
  11. data2 = data[:]
  12. data2.sort(key=lambda x: (x[1], -x[2]))
  13. print(data2)
复制代码

四、反转:reverse、reversed与[::-1]
reverse()原地反转列表并返回None,额外内存为O(1);reversed()返回反向迭代器,不修改原列表,迭代器耗尽后再转列表会为空;[::-1]创建反转后的新列表,额外内存为O(n)。选择建议:需要修改原列表用reverse;只需要反向遍历用reversed;需要保留原列表且要新列表用[::-1]或list(reversed(lst))。
  1. nums = [1, 2, 3, 4, 5]
  2. nums.reverse()
  3. print(nums)
  4. nums = [1, 2, 3, 4, 5]
  5. rev = reversed(nums)
  6. print(list(rev))
  7. print(list(rev))
  8. print(nums)
  9. for num in reversed(nums):
  10.     print(num, end=' ')
  11. print()
  12. new_list = nums[::-1]
  13. print(new_list)
  14. print(nums)
复制代码

五、实战:排行榜与优先级任务队列
排行榜维护(name,score)列表,每次添加后按分数降序sort;get_rank用enumerate找名次;get_top_n用切片取前N;get_around_player按排名截取周边玩家。该类把排序、查找和切片组合在一起,适合脚本中的简单排名场景。
  1. class Leaderboard:
  2.     def __init__(self):
  3.         self._entries = []
  4.     def add_score(self, name, score):
  5.         self._entries.append((name, score))
  6.         self._entries.sort(key=lambda x: x[1], reverse=True)
  7.     def get_rank(self, name):
  8.         for i, (n, s) in enumerate(self._entries):
  9.             if n == name:
  10.                 return i + 1
  11.         return -1
  12.     def get_top_n(self, n):
  13.         return self._entries[:n]
  14.     def get_around_player(self, name, radius=2):
  15.         rank = self.get_rank(name)
  16.         if rank == -1:
  17.             return []
  18.         idx = rank - 1
  19.         start = max(0, idx - radius)
  20.         end = min(len(self._entries), idx + radius + 1)
  21.         return self._entries[start:end]
  22. lb = Leaderboard()
  23. lb.add_score('小明', 850)
  24. lb.add_score('小红', 920)
  25. lb.add_score('小刚', 780)
  26. lb.add_score('小李', 950)
  27. lb.add_score('小王', 880)
  28. lb.add_score('小张', 830)
  29. print(lb.get_rank('小李'))
  30. print(lb.get_top_n(3))
  31. print(lb.get_around_player('小刚', 1))
复制代码
优先级任务队列用sort加key排序:priority小的优先,同优先级按deadline排序;get_next_task排序后pop(0);find_tasks_by_priority用列表推导过滤。
  1. class PriorityTaskQueue:
  2.     def __init__(self):
  3.         self._tasks = []
  4.     def add_task(self, name, priority, deadline):
  5.         self._tasks.append({
  6.             'name': name,
  7.             'priority': priority,
  8.             'deadline': deadline,
  9.         })
  10.     def get_next_task(self):
  11.         if not self._tasks:
  12.             return None
  13.         self._tasks.sort(key=lambda t: (t['priority'], t['deadline']))
  14.         return self._tasks.pop(0)
  15.     def find_tasks_by_priority(self, priority):
  16.         return [t for t in self._tasks if t['priority'] == priority]
  17.     def pending_count(self):
  18.         return len(self._tasks)
  19. queue = PriorityTaskQueue()
  20. queue.add_task('发送邮件', 2, 1000)
  21. queue.add_task('数据库备份', 1, 900)
  22. queue.add_task('生成报表', 2, 850)
  23. queue.add_task('清理日志', 3, 1200)
  24. while queue.pending_count() > 0:
  25.     task = queue.get_next_task()
  26.     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迭代器耗尽等常见问题。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python列表查找排序反转:index与sort区别

这组方法的坑总结得挺到位,尤其是返回值这一块。我最早写脚本时就栽过 nums = nums.sort() 这种写法,直接把列表变成 None,排查半天才反应过来。reverse 和 sort 都是原地改,返回 None 这个习惯确实得刻在脑子里,不然链式调用特别容易出问题。 查找部分我最常用的是 index 包装成自己的 safe_index,生产脚本里到处 try/except 太啰嗦了,抽一个默认返回 -1 的函数,后面重复用起来很舒服。find_first 用 enumerate 配 predicate 这个思路也好,比先 in 判断再 index 少遍历一次,还能顺手把命中的元素返回出来,省得再取一遍。 排序那里,字典列表按字段排是最常见的场景,我一般条件允许就上 itemgetter,写起来短,也确实比 lambda 快一点,尤其是数据量上万之后差距能感觉到。多级排序的 (-s[1], s[2]) 这个负号技巧很实用,成绩降序、班级升序一个 key 就搞定,不用分两次排。还有个可以补充的点:sort 本身是稳定排序,所以如果记不住元组拼 key 的写法,也可以先从次要字段排一次,再按主要字段排一次,结果是一样的,改起来更直观。 条件排序那个 key=lambda x: (x % 2, x) 挺巧的,偶数在前奇数在后且各自有序,这种把布尔或取余结果塞进元组当第一级键的做法,
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python列表查找排序反转:index与sort区别

楼主整理得挺系统的,正好把这几个容易混淆的点串起来了。index 找不到抛 ValueError 这点确实坑过不少人,safe_index 这种包装在脚本里很实用;sort 原地返回 None,导致 result = nums.sort() 拿到 None 也是经典错误,用 sorted 更稳。key 参数那部分用 (-成绩, 班级) 做多级排序的思路很清晰,itemgetter 比 lambda 快这一点也值得记下来。 补充两个小细节:用 max(set(lst), key=lst.count) 求众数时,如果列表为空会直接报错,而且有并列时取到哪个元素并不保证,实际脚本里用 Counter.most_common 更直观;另外 reversed 返回的迭代器只能消费一次,转 list 之前别先遍历过一遍,否则会得到空结果。帖子最后 operator 那段好像被截断了,期待楼主补完整。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python列表查找排序反转:index与sort区别

整理得很细,尤其是把 index 抛 ValueError、sort 和 reverse 原地修改返回 None、reversed 迭代器只能消费一次这几个点单独拎出来讲,确实都是实际写脚本时最容易踩的坑。安全查找那几个封装函数可以直接拿去用,find_first 用 enumerate 支持条件查找比先 in 再 index 更省一次遍历,这个思路很实用。 排序部分把 sort 和 sorted 的选择规则讲清楚了,key 参数配合元组做多级排序、以及 (x % 2, x) 这种条件排序的例子也很直观。itemgetter 和 attrgetter 在 C 层实现、通常比 lambda 快这一点值得记住,字典列表按字段排序时优先用它们。 另外提醒一下,帖子最后 operator 那段好像没贴完,停在 st 就断了,可以补上完整的 itemgetter 示例。还有 reversed 只能消费一次,如果后面要多次使用,可以顺手提一句用 list(reversed(...)) 或者切片 [::-1] 转成列表,避免第二次遍历拿到空结果。整体内容对入门和日常脚本都挺有帮助的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-6 05:31 , Processed in 0.020121 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部