在Python编程中,排序是数据处理的常用操作。Python提供了两个排序工具:list.sort()(原地排序,直接修改原列表)和sorted()(返回新排序列表,不修改原数据)。其中sorted()功能更灵活,能对任意可迭代对象进行排序,并通过key参数实现自定义排序规则。
- >>> numbers = [3, 1, 4, 1, 5, 9, 2, 6]
- >>> sorted_numbers = sorted(numbers)
- >>> sorted_numbers
- [1, 1, 2, 3, 4, 5, 6, 9]
- >>> numbers # 原列表不变
- [3, 1, 4, 1, 5, 9, 2, 6]
复制代码
list.sort()原地修改,而sorted()返回新列表,可作用于任何可迭代对象。
一、sorted()基本参数
sorted(iterable, key=None, reverse=False)。reverse控制升降序:- >>> sorted([3,1,4,1,5], reverse=True)
- [5, 4, 3, 1, 1]
复制代码
key参数是排序的灵魂——接收一个函数,对每个元素返回一个用于比较的值。原始元素不变,排序依据key的返回值。- >>> words = ["banana", "apple", "Cherry", "date", "Elderberry"]
- >>> sorted(words, key=str.lower) # 忽略大小写
- ['apple', 'banana', 'Cherry', 'date', 'Elderberry']
- >>> sorted(words, key=len)
- ['date', 'apple', 'banana', 'Cherry', 'Elderberry']
- >>> import re
- >>> codes = ["item2", "item10", "item1", "item20"]
- >>> def extract_number(s):
- ... match = re.search(r'\d+', s)
- ... return int(match.group()) if match else 0
- >>> sorted(codes, key=extract_number)
- ['item1', 'item2', 'item10', 'item20']
复制代码
二、lambda作为key——最常用的排序方式
对字典列表按字段排序:- >>> employees = [
- ... {"name": "张三", "age": 28, "salary": 15000, "department": "技术部"},
- ... {"name": "李四", "age": 35, "salary": 20000, "department": "管理部"},
- ... {"name": "王五", "age": 22, "salary": 12000, "department": "技术部"},
- ... {"name": "赵六", "age": 30, "salary": 18000, "department": "市场部"},
- ... {"name": "钱七", "age": 25, "salary": 13000, "department": "技术部"},
- ... ]
- >>> sorted(employees, key=lambda e: e["age"])
复制代码
多级排序:key返回元组,Python按元组元素依次比较。降序数字字段可用负号。- >>> sorted(employees, key=lambda e: (e["department"], -e["salary"]))
复制代码
注意:对非数字字段的降序不能直接用负号,可借助排序稳定性,先按次要key升序排,再按主要key降序排(反向两次sorted)。
三、使用operator模块
itemgetter和attrgetter比lambda更简洁高效:- >>> from operator import itemgetter, attrgetter
- >>> sorted(employees, key=itemgetter("department", "salary"))
- >>> class Student:
- ... def __init__(self, name, score):
- ... self.name = name
- ... self.score = score
- >>> students = [Student("Alice",85), Student("Bob",92), Student("Charlie",78)]
- >>> sorted(students, key=attrgetter("score"), reverse=True)
复制代码
四、自定义对象的排序
方法一:在类中实现__lt__(小于)方法,即可直接使用sorted()。- >>> from functools import total_ordering
- >>> class Task:
- ... def __init__(self, name, priority, due_date):
- ... self.name = name
- ... self.priority = priority
- ... self.due_date = due_date
- ... def __lt__(self, other):
- ... if self.priority != other.priority:
- ... return self.priority < other.priority
- ... return self.due_date < other.due_date
- ... def __repr__(self):
- ... return f"Task({self.name}, pri={self.priority}, due={self.due_date})"
- >>> sorted(tasks)
复制代码
方法二:使用key lambda,无需修改类定义:- >>> sorted(tasks, key=lambda t: (t.priority, t.due_date))
复制代码
五、实战案例
1. 日志按时间排序
从日志行中提取时间戳,作为key。若需按时间后再按日志级别排序,可返回元组。- >>> logs = [
- ... "[2024-03-15 14:30:22] ERROR 数据库连接失败",
- ... "[2024-03-15 14:25:10] INFO 服务器启动",
- ... "[2024-03-15 14:32:05] WARNING 内存使用率85%",
- ... "[2024-03-15 14:25:10] DEBUG 加载配置文件",
- ... "[2024-03-15 14:28:45] INFO 用户登录成功",
- ... ]
- >>> def extract_timestamp(log_line):
- ... return log_line[1:20]
- >>> LEVEL_PRIORITY = {"DEBUG": 0, "INFO": 1, "WARNING": 2, "ERROR": 3}
- >>> def sort_key(log_line):
- ... ts = extract_timestamp(log_line)
- ... level = log_line[22:].split()[0]
- ... return (ts, LEVEL_PRIORITY.get(level, 99))
- >>> sorted(logs, key=sort_key)
复制代码
2. 自然排序
人类期望的排序如file1, file2, ..., file10,而非字典序的file1, file10, file11, file2。通过正则拆分数字和文本。- >>> import re
- >>> def natural_sort_key(s):
- ... parts = re.split(r'(\d+)', s)
- ... return [int(part) if part.isdigit() else part.lower() for part in parts]
- >>> files = ["file10.txt", "file1.txt", "file2.txt", "file20.txt", "file11.txt"]
- >>> sorted(files, key=natural_sort_key)
- ['file1.txt', 'file2.txt', 'file10.txt', 'file11.txt', 'file20.txt']
复制代码
六、总结
sorted()的key参数是排序核心,通过返回不同比较值实现任意规则排序。元组实现多级排序,利用稳定性可构造复杂排序。lambda和operator模块是常用工具。掌握这些,就能轻松应对各种排序需求。 |