Python 集合的遍历,推导及 filter/map/reduce 操作 中讲了对集合的 filter, map 和 reduce 操作,那还有 sort 排序呢?像 Java 一样,Python 也提供了 sort() 和 sorted() 方法。
sort() 是 list 的实例方法, sorted() 是一个内置函数。Python 中也是只有 list 才有顺序。
list.sort() 方法
查看 Python 3 中的 list.sort() 方法(help(list.sort))
Help on method_descriptor:
sort(self, /, *, key=None, reverse=False)
Stable sort *IN PLACE*.
Python 的 list.sort() 方法和 Java List.sort() 方法一样的,都是 IN PLACE 排序,没有返回值。实际看下各种排序场景
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | >>> s1 = [2, 1, 5, 3, 4] >>> s1.sort() >>> s1 [1, 2, 3, 4, 5] >>> s.sort(reverse=True) >>> s [5, 4, 3, 2, 1] >>> s2 = [('c', 2), ('a', 1), ('b', 5), ('e', 3), ('d', 4)] >>> s2.sort() >>> s2 [('a', 1), ('b', 5), ('c', 2), ('d', 4), ('e', 3)] >>> s2.sort(key=lambda item: item[1]) >>> s2 [('a', 1), ('c', 2), ('e', 3), ('d', 4), ('b', 5)] >>> s2.sort(key=lambda item: item[1], reverse=True) >>> s2 [('b', 5), ('d', 4), ('e', 3), ('c', 2), ('a', 1)] | 
list.sort() 能直接对可比较比象进行排序,也就是两个对象间能用 < 号相比较,映射到函数就是 __lt__。数字字符串都能比,从前面看到两个 tuple 也可以比较,
| 1 2 | >>> ('c', 2) < ('a', 1) False | 
如果两个元素之间不能进行比较的话,可以指定 key 参数来取出相关值进行比较,如上面的 key=lambda item: item[1], 取 tuple 的第二个元素进行 < 对较
再来看自定义对象的比较
| 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 | >>> class User: ...     def __init__(self, name, email): ...         self.name = name ...         self.email = email ... ...     def get_name(self): ...         return self.name ... ...     def __repr__(self): ...         return f'User: name={self.name}, email={self.email}' ... ... ... >>> users = [User('cc', 'hh@g.com'), User('aa', 'ii@g.com'), User('bb', 'ee@g.com')] >>> users.sort() Traceback (most recent call last):   File "<input>", line 1, in <module>     users.sort() TypeError: '<' not supported between instances of 'User' and 'User' >>> users.sort(key=lambda user: user.get_name()) >>> users [User: name=aa, email=ii@g.com, User: name=bb, email=ee@g.com, User: name=cc, email=hh@g.com] >>> users.sort(reverse=True) Traceback (most recent call last):   File "<input>", line 1, in <module>     users.sort(reverse=True) TypeError: '<' not supported between instances of 'User' and 'User' | 
users.sort() 不管 reverse 是 False 还是 True,始终是试图用 < 去比较两个对象。由于 User 没有定义 __lt__ 方法,所以不能直接对 User 进行排序,而必须指定 key=lambda user: user.get_name() 依照 get_name() 的返回值进行排序。
如何让对象是可比较的呢,或者直截说验证一下 Python 是否是调用了 __lt__ 方法来比较函数。接下来动态的给 User 类添加一个  __lt__ 方法,然后变 users.sort() 从不可能到可能
| 1 2 3 4 5 6 7 8 9 10 | >>> def little(self, that): ...     print(f'compare {self}, {that}') ...     return self.email < that.email ... >>> setattr(User, '__lt__', little) >>> users.sort() compare User: name=cc, email=hh@g.com, User: name=bb, email=ee@g.com compare User: name=aa, email=ii@g.com, User: name=cc, email=hh@g.com >>> users [User: name=bb, email=ee@g.com, User: name=cc, email=hh@g.com, User: name=aa, email=ii@g.com] | 
这时候发现不用指定 key 对 users 也可以排序了,比较时调用了 __lt__ 方法。
前面提到过只能对 list 进行排序,也就是只有 list 才有 sort() 方法,这也不难理解,set, tuple, dict 是不存在顺序观念的。但有意思的是,在对 set, tuple 转换成 list 后便自动立马有了顺序。
| 1 2 3 4 5 6 | >>> s = {2, 1, 3} >>> list(s) [1, 2, 3] >>> t = (2, 1, 3, 2) >>> list(t) [2, 1, 3, 2] | 
这也为接下来要看到的 sorted() 函数作了一个铺垫。前面只有 list 才有 sort() 方法,然而内置的 sorted() 函数除了对 list 进行排序,对 set 和 tuple 也能排序,效果上相当于转换为 list,再对 list 排序,所以最终 sorted() 后得到的都是一个 list。
内置的 sorted() 函数
同样,用 help(sorted) 看下它的原型
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
之于 list.sort() 方法,我们只需记住 sorted() 的几个不同之处
- 它是一个内置函数,所以第一个参数传入待排序的集合
- 待排序的集合不受限于 list, 它是 iterable, 根据鸭子类型的规则,只要该类实现了 __iter__()方法就是 iterable。具体说来就是能使用for..in..进行遍历的都能排序,像 set, tuple, 或 dict.items() 等
- 排序不再是 IN PLACE, 也就是它的排序不改变原集合,而是生成一个新的 list 并返回
其他的排序行为与list.sort() 函数是一致的。
看下执行效果,对 list, tuple 和 set 进行排序
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | >>> s = [2, 1, 5, 3,4] >>> nums = [2, 1, 5, 3, 4] >>> new_nums = sorted(nums) >>> nums [2, 1, 5, 3, 4] >>> new_nums [1, 2, 3, 4, 5] >>> t = (2, 1, 5, 3, 4) >>> t1 = sorted(t) >>> t (2, 1, 5, 3, 4) >>> t1 [1, 2, 3, 4, 5] >>> type(t1) <class 'list'> >>> s = {2, 1, 5, 3, 4} >>> s1 = sorted(s) >>> s {1, 2, 3, 4, 5} >>> s1 [1, 2, 3, 4, 5] >>> type(s1) <class 'list'> | 
不用理会 Python 怎么显示 set 中的内容,因为 set 是不能用索引号访问的。无论是对 tuple 还是 set 排序后得到的结果都是 list,sorted() 对原集合是无副作用的。
对比一下 Java 的 sort() 与 sorted() 方法
先看一下 Java 的这两个方法, Java 的 Collections 的类提供了两个 sort 方法
| 1 2 3 4 5 6 7 | public static <T extends Comparable<? super T>> void sort(List<T> list) {     list.sort(null); } public static <T> void sort(List<T> list, Comparator<? super T> c) {     list.sort(c); } | 
它们接受的参数是 List, 所以也是只针对 List 进行排序,也是 IN PLACE 的, 也是无返回值的。sorted() 出现在 Java 的 Stream
它得到的结果还是 Stream,因此它也是无副作用的,最后需要 collect(tolist()) 得到一个新的集合。
关于排序算法
Python 和 Java 对 List 进行排序的算法都是 Timsort, 摘录一下中文维基百科中的内容
Timsort 是一种混合稳定的排序算法,源自合并排序和插入排序,旨在较好地处理真实世界中各种各样的数据。它使用了 Peter Mcllroy 的"乐观排序和信息理论上复杂性"中的技术,参见 第四届年度ACM-SIAM离散算法研讨会论文集,第467-474页,1993年。 它由 Tim Peters 在2002年实现,并应用于 Python编程语言。该算法通过查找已经排好序的数据子序列,在此基础上对剩余部分更有效地排序。 该算法通过不断地将特定子序列(称为一个 run )与现有的 run 合并,直到满足某些条件为止来达成的更有效的排序。 从 2.3 版本起,Timsort 一直是 Python 的标准排序算法。 它还被 Java SE7[4], Android platform[5], GNU Octave,[6] 谷歌浏览器,[7] 和 Swift[8] 用于对非原始类型的数组排序。
本文链接 https://yanbin.blog/python-sort-list-sort-sorted/, 来自 隔叶黄莺 Yanbin Blog
[版权声明]  本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
