Python案例:如何自定义排序规则?一文掌握核心技巧
目录导读
- 为什么需要自定义排序规则?
- 内置排序函数与基础用法
- 核心方法:key参数详解
- 高级技巧:使用cmp_to_key实现旧式比较
- 实战案例:多种数据结构的自定义排序
- 性能优化与常见陷阱
- 问答环节:解决你的排序困惑
- 总结与拓展练习
为什么需要自定义排序规则?
在实际开发中,我们常常遇到默认排序无法满足需求的情况。

- 对包含中文的书籍列表按拼音排序
- 根据商品价格优先、销量次之的复合排序
- 对包含None值的列表进行安全排序
- 按文件修改时间或自定义优先级排序
Python内置的sorted()和list.sort()虽然强大,但默认只能按数字大小、字母顺序等基础规则排序。自定义排序规则就像给排序算法一个“裁判”,告诉它如何比较两个元素。
问答Q1:为什么不用直接修改列表元素类型? A1:类型转换会丢失原有数据结构,且对于复杂对象(如自定义类实例)无法通过简单转换解决问题,自定义规则保持数据不变,只改变比较逻辑。
内置排序函数与基础用法
1 sorted()与list.sort()的区别
# sorted()返回新列表 data = [3, 1, 4, 1, 5] new_list = sorted(data) # [1, 1, 3, 4, 5] print(data) # 原列表不变 [3, 1, 4, 1, 5] # sort()直接修改原列表 data.sort() # 现在data变成了 [1, 1, 3, 4, 5]
2 默认排序的局限性
# 混合类型排序会报错
mixed = [3, "1", 2] # TypeError: '<' not supported between instances of 'str' and 'int'
# 复杂对象无法比较
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
students = [Student("Alice", 85), Student("Bob", 92)]
# sorted(students) # 错误!Student对象无法比较
核心方法:key参数详解
key参数是自定义排序最常用、最高效的方式,它接受一个函数,该函数接收一个列表元素,返回一个用于排序的“键值”。
1 基本语法
sorted(iterable, key=function, reverse=False)
2 经典案例:按字符串长度排序
words = ["apple", "banana", "cherry", "date"] sorted_words = sorted(words, key=len, reverse=True) # 输出: ['banana', 'cherry', 'apple', 'date']
3 利用lambda匿名函数
# 按元组第二个元素排序 pairs = [(1, 'z'), (2, 'a'), (3, 'm')] sorted_pairs = sorted(pairs, key=lambda x: x[1]) # 输出: [(2, 'a'), (3, 'm'), (1, 'z')]
4 多级排序(优先级排序)
通过返回元组(tuple)来实现多级排序:
# 先按优先级(数字小优先),再按创建时间
tasks = [
{'priority': 3, 'time': 100},
{'priority': 1, 'time': 200},
{'priority': 2, 'time': 150},
]
sorted_tasks = sorted(tasks, key=lambda t: (t['priority'], t['time']))
问答Q2:key参数与cmp(旧式比较)哪个更好? A2:key参数更快,因为Python会对每个元素只调用一次key函数并缓存结果,而cmp会多次调用比较函数,官方推荐优先使用key。
高级技巧:使用cmp_to_key实现旧式比较
Python 3去掉了sorted()的cmp参数,但可以用functools.cmp_to_key()模拟。
1 适用场景
当比较逻辑无法简单映射到单一键值时(例如需要比较两个元素的相对顺序),用cmp_to_key。
from functools import cmp_to_key
def compare(x, y):
# 自定义比较规则:奇数排前面,奇数内部升序,偶数排后面
if x % 2 == 1 and y % 2 == 0:
return -1
elif x % 2 == 0 and y % 2 == 1:
return 1
else:
return x - y
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
sorted_numbers = sorted(numbers, key=cmp_to_key(compare))
# 输出: [1, 1, 3, 5, 9, 2, 4, 6]
2 与key的比较
| 特征 | key参数 | cmp_to_key |
|---|---|---|
| 性能 | 快(缓存键值) | 慢(每次比较都调用) |
| 易用性 | 简单直观 | 需写比较函数 |
| 适用性 | 可生成可比较键 | 需要两两比较逻辑 |
实战案例:多种数据结构的自定义排序
案例1:自定义类的排序
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
rating: float
products = [
Product("Phone", 699.0, 4.5),
Product("Laptop", 1299.0, 4.8),
Product("Tablet", 399.0, 4.2),
]
# 按价格降序排列
products.sort(key=lambda p: p.price, reverse=True)
# 或更Pythonic:使用attrgetter
from operator import attrgetter
products.sort(key=attrgetter('price'))
案例2:中文排序(拼音)
# 需要安装pypinyin库 from pypinyin import lazy_pinyin cities = ["北京", "上海", "广州", "深圳"] sorted_cities = sorted(cities, key=lambda c: lazy_pinyin(c)[0]) # 输出: ['北京', '广州', '上海', '深圳']
案例3:字典列表的复合排序
data = [
{'name': 'Alice', 'age': 30, 'salary': 50000},
{'name': 'Bob', 'age': 25, 'salary': 60000},
{'name': 'Charlie', 'age': 30, 'salary': 55000},
]
# 先按年龄升序,年龄相同按薪水降序
sorted_data = sorted(data, key=lambda x: (x['age'], -x['salary']))
案例4:处理None值的安全排序
numbers = [3, None, 1, None, 2]
# 将None视为最小或最大
sorted_with_none = sorted(numbers, key=lambda x: (x is None, x))
# (False, 数值) 排在 (True, None) 前,输出: [1, 2, 3, None, None]
# 或:None视为无穷大
sorted_none_last = sorted(numbers, key=lambda x: (x is None, x if x is not None else float('inf')))
性能优化与常见陷阱
1 使用内置函数替代lambda
import operator
# 不推荐:lambda每次调用创建函数对象
students.sort(key=lambda s: s.age)
# 推荐:使用attrgetter
students.sort(key=operator.attrgetter('age'))
# 对于字典键,使用itemgetter
dicts.sort(key=operator.itemgetter('price'))
2 避免在key函数中做复杂计算
# 错误:每次比较都重新计算 sorted(data, key=lambda x: expensive_function(x)) # 改进:对每个元素只计算一次 keys = [expensive_function(x) for x in data] sorted_data = [x for _, x in sorted(zip(keys, data))]
3 常见陷阱
- key函数必须返回可比较对象:例如不能返回None或混合类型
- 稳定性问题:Python的排序是稳定的(相等元素保持原顺序),利用这一点可以实现多级排序
- 可变对象作键:排序后修改键值不影响排序结果(因为排序已结束)
问答Q3:为什么我的排序结果不对? A3:常见原因包括:key函数返回类型不一致、比较逻辑有缺陷、忽略了reverse参数,建议用print调试key函数的返回值。
问答环节:解决你的排序困惑
Q4:如何对文件按修改时间排序?
import os, glob
files = glob.glob("*.txt")
sorted_files = sorted(files, key=os.path.getmtime) # 按修改时间升序
# 注意:getmtime可能在文件不存在时报错,实际使用需加try
Q5:能否对嵌套字典进行深度排序?
nested = {
'group1': [3, 1, 2],
'group2': [1, 9, 5],
'group3': [4, 2, 7],
}
# 按每个组的中位数排序
sorted_groups = sorted(nested.items(), key=lambda x: sorted(x[1])[len(x[1])//2])
Q6:排序时如何忽略大小写?
words = ["apple", "Banana", "Cherry", "date"] sorted_words = sorted(words, key=str.lower) # 统一转小写比较
总结与拓展练习
核心要点回顾
- key参数:最推荐的自定义排序方法,返回排序键值
- lambda表达式:快速定义简单的key函数
- operator模块:itemgetter, attrgetter提升性能
- cmp_to_key:处理复杂比较逻辑的备用方案
- 多级排序:通过返回元组实现优先级排序
拓展练习(动手试试)
- 任务1:对一个列表
["2021-01-03", "2020-05-10", "2022-11-20"]按日期排序,忽略字符串格式 - 任务2:对
[(2, 3), (1, 4), (3, 1)]按元组乘积降序排列 - 任务3:自定义类
Book有title和pages属性,按书名长度降序,长度相同按页数升序
提示:任务1可用key=lambda d: tuple(map(int, d.split("-")));任务2返回x[0]*x[1];任务3用(-len(title), pages)作为键。
通过本文的详细讲解,你应该已经掌握了Python自定义排序的核心理念和实战技巧。排序规则的本质是为每个元素生成一个可比较的“指纹”,无论是通过key、cmp_to_key还是其他方式,核心都是设计出能正确反映排序需求的比较逻辑,多动手练习,你会发现在数据处理、算法实现等场景中,自定义排序都是不可或缺的利器。