Python排序详解:sorted() vs list.sort()
在Python中排序数据时,我们通常会使用sorted()和list.sort()这两个函数。它们存在一些关键的区别,适用于不同的场景。本文将通过详细的示例阐述两者的差异,以及何时使用哪个函数更加合适。
list.sort() 的特点
list.sort()是列表对象的一个方法,作用是就地(in-place)对列表进行排序。也就是说,它直接修改了原列表,没有返回值:
1 2 3
| nums = [3, 1, 2] nums.sort() print(nums)
|
list.sort()支持一些可选参数来自定义排序:
reverse=True 倒序排序
key 根据自定义函数结果排序
例如:
1 2 3 4 5 6 7
| nums = [3,-2,1]
nums.sort(reverse=True) print(nums)
nums.sort(key=abs) print(nums)
|
list.sort()只能用于列表,不能用于其他可迭代对象。它通过下标排序,稳定性不确定,相同元素的相对顺序可能会改变。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from dataclasses import dataclass
@dataclass class User: name: str age: int
users = [ User('John', 25), User('Alice', 25), User('Bob', 24) ]
users.sort(key=lambda x: x.age)
print([u.name for u in users])
|
按照年龄排序,John和Alice的年龄相同,但是这是两个不同的人,list.sort()可能不会保留原顺序。
list.sort()主要适用于:就地排序列表,不需要保留原顺序。
sorted() 的特点
sorted()是一个全局的内置函数,可以对任意可迭代对象进行排序,返回一个新列表。原列表不变:
1 2 3 4 5
| nums = [3, 1, 2] new_nums = sorted(nums)
print(new_nums) print(nums)
|
sorted()也可以接受 reverse 和 key 自定义排序:
1 2 3 4 5
| new_nums = sorted(nums, reverse=True) print(new_nums)
new_nums = sorted(nums, key=str) print(new_nums)
|
sorted() 是一个稳定排序,相同元素的相对顺序会保持不变。
主要适用于:
- 需要保留原列表顺序的场景
- 对不可变对象如 tuple 进行排序
- 链式调用多个排序操作
- 需要自定义排序逻辑的场景
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from pprint import pprint
users = [{'name':'John', 'age':20}, {'name':'John', 'age':15}, {'name':'Lisa', 'age':25}]
by_age = sorted(users, key=lambda u: u['age'])
by_age_name = sorted(by_age, key=lambda u: u['name'])
pprint(by_age_name)
|
使用 attrgetter 和 itemgetter 代替 lambda 函数
在 key 参数中传入 lambda 函可以实现自定义排序,但是使用 operator 模块的 attrgetter 和 itemgetter 可以使代码更简洁:
itemgetter 其实就是重载了[]下标操作符,可以用于获取序列的下标和字典的键:
1 2 3 4 5 6 7 8 9
| from pprint import pprint from operator import itemgetter
users = [{'name':'John', 'age':20}, {'name':'John', 'age':15}, {'name':'Lisa', 'age':25}]
by_age_name = sorted(users, key=itemgetter( 'name','age')) pprint(by_age_name)
|
attrgetter 可以用于获取对象属性:
1 2 3 4 5 6 7 8 9 10 11 12
| from operator import attrgetter from pprint import pprint
users = [ User('John', 25), User('Alice', 25), User('Bob', 24) ]
by_age = sorted(users, key=attrgetter('age')) pprint(by_age)
|
它们提供了一种更优雅和高效的提取对象属性和字典键的方式。
attrgetter 和 itemgetter 性能如何?
我们可以写个简单的测试来比较一下在sorted()中使用lambda和attrgetter的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import timeit
class Person: def __init__(self, name, age): self.name = name self.age = age
people = [ Person("Diana", 40), Person("Frank", 45), Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35), Person("Eve", 20) ]
lambda_time = timeit.timeit( stmt="sorted(people, key=lambda person: person.age)", setup="from __main__ import people", number=100000 )
attrgetter_time = timeit.timeit( stmt="sorted(people, key=attrgetter('age'))", setup="from __main__ import people; from operator import attrgetter", number=100000 )
print(f"Using lambda: {lambda_time:.6f} seconds") print(f"Using attrgetter: {attrgetter_time:.6f} seconds")
|
可以看到,使用 attrgetter 的性能比 lambda 函数更好,因为底层是C语言实现的。
总结
list.sort() 用于就地排序列表,更简单直接
sorted() 更通用更灵活,可以保留原顺序
attrgetter 和 itemgetter 可以简化 key 函数,同时提高性能
根据实际需求选择合适的排序函数,可以让代码更高效和可读!