初探 sort 方法和 sorted 内置函数

本文主要介绍 Python 中 sort 方法和 sorted 函数的基本用法、高级用法、Timsort 算法的简单介绍、二者异同等。阅读本文预计 6 min.

1. 前言

Python 中列表的 sort 方法和内置函数 sorted 非常有用。自己以前只是用到了一点皮毛,没有进一步深入了解。今天刚好看了相关的知识,对于 sort 和 sorted 有了进一步的认知,就总结了一篇,大家一起学习交流。

2. sort 方法和 sorted 函数的基本用法

先简单说明一下方法(Method)和函数(Function),很多时候大多数人不区分方法和函数,基本当做一回事,这其实没啥问题。但是看 Python 官方文档的时候,有时候出现 Method 有时候又是 Function,让人很头疼,所以区分一下方法和函数还是有好处的。其实区分也比较简单。

方法(Method):是通过 obj.funcname() 来引用调用。如:列表的 sort 方法,调用时就是 list.sort()。
函数(Function):是通过 funcname() 直接调用。如内置函数(built-in function) sorted,调用时就是 sorted()。

注:Python API 的一个惯例(convention)是:如果一个函数或者方法是原地改变对象,那么应该返回 None。这么做的目的是为了告诉调用者对象被原地改变了。这个约定的弊端是无法级联调用(cascade call)这些方法。而返回新对象的方法可以级联调用,从而形成连贯的接口(fluent interface)。

接下来,进入 sort 方法和 sorted 基本使用的学习。

  • list.sort():对列表进行原地(in place)排序,默认是升序,返回值是 None。
  • sorted():对可迭代对象进行排序,默认是升序,并把排序结果作为一个新的列表返回,原迭代对象顺序不受影响。

举栗子:

>>> list_a = [1, 5, 6, 4, 2, 3]
>>> sorted(list_a)  # 返回一个新的排好序的 list
[1, 2, 3, 4, 5, 6]
>>> list_a  # list_a 不改变
[1, 5, 6, 4, 2, 3]
>>> print(list_a.sort())  # list_a.sort() 就地排序,返回值是 None
None
>>> list_a  # list_a 发生改变
[1, 2, 3, 4, 5, 6]

此外,sorted 函数除了用于列表,还可用于其他可迭代对象,如元组、生成器等。

>>> sorted((1, 5, 9, 2, 6, 8, 7))  # 对元组排序
[1, 2, 5, 6, 7, 8, 9]
>>> sorted('afbcedh')  # 对字符串排序
['a', 'b', 'c', 'd', 'e', 'f', 'h']
>>> list(range(10,0,-1))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> sorted(range(10,0,-1))  # 对生成器排序
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
[1, 2, 3, 4, 5]

以上就是 sort 方法和 sorted 函数最基本的用法。可以发现都是升序排列的,如果我们想降序排列怎么办?

3. 可选仅限关键字参数 reverse

sort 方法和 sorted 函数都可以接收一个可选仅限关键字参数(keyword-only arguments) reverse,用于指定是升序(Ascending)还是降序(Descending)。默认 reverse=False 即升序排序。

举栗子:

>>> list_a = [3, 1, 2, 4]
>>> sorted(list_a)  # 默认升序
[1, 2, 3, 4]
>>> sorted(list_a, reverse=True)  # 降序排序
[4, 3, 2, 1]
>>> list_a  # list_a 不变
[3, 1, 2, 4]
>>> list_a.sort()  # 默认原地升序排序
>>> list_a
[1, 2, 3, 4]
>>> list_a.sort(reverse=True)  # 原地降序排序
>>> list_a
[4, 3, 2, 1]

注: 仅限关键字参数 即只能通过 keyword=value 的形式传参的参数叫仅限关键字参数,有关 Python 函数仅限关键字参数、仅限位置参数、可变参数、默认参数、位置参数、可变关键字参数等可以参看我的另一篇总结。 一文了解Python函数

接下来我们介绍另一个更加强大的可选仅限关键字参数 key。

4. 可选仅限关键字参数 key

sort 方法和 sorted 函数还可以接收一个可选仅限关键字参数 key,key 是一个只有一个参数的函数,这个函数会依次作用于序列的每一个元素,并将所得的结果作为排序的依据。key 默认是 None,即恒等函数(identity function),也就是默认用元素自己的值排序。

举栗子:

>>> list_a = ['This', 'is', 'a', 'test', 'string', 'from', 'Andrew']
>>> sorted(list_a, key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
>>> list_a
['This', 'is', 'a', 'test', 'string', 'from', 'Andrew']

这里的 key=str.lower 就是把 list_a 中的每个元素都用 str.lower 方法处理,即:都转为小写,然后再进行比较排序。

对于复杂的数据对象,我们可以用对象的索引,取对象的一部分作为比较的 key,这时匿名函数会帮助我们,举栗子:

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2])   # 根据年龄排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

我们还可以取对象的属性作为比较的 key,举栗子:

>>> class Student:
...     def __init__(self, name, grade, age):
...         self.name = name
...         self.grade = grade
...         self.age = age
...     def __repr__(self):
...         return repr((self.name, self.grade, self.age))
...
>>> student_objects = [
...     Student('john', 'A', 15),
...     Student('jane', 'B', 12),
...     Student('dave', 'B', 10),
... ]
>>> sorted(student_objects, key=lambda student: student.age)   # 按年龄进行排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

可以看到 key 非常的强大!使得复杂的数据结构可以按照我们自己的意愿进行排序。
list.sort、sorted、max 和 min 中可选仅限关键字参数 key 的设计非常棒!使用 key 会更加高效且简洁。简单是指你只需要定义一个只有一个参数的函数,用于排序即可。高效是指 key 的函数在每个元素上只会调用一次,而双参数比较函数,则每次两两比较的时候都会被调用。(PS:这一点还不是特别理解)

5. operator 模块的使用

因为通过索引或者属性以及函数来排序非常常用,所以 Python 内置的 operator 模块提供了 itemgetter(), attrgetter() 和 methodcaller() 函数来更加简单而快速的实现相关的功能。

举栗子:

>>> from operator import itemgetter, attrgetter  # 导入相关的函数
>>> sorted(student_tuples, key=itemgetter(2))  # 按索引取值排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
>>> sorted(student_objects, key=attrgetter('age'))  # 按属性取值排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

此外,通过 operator 模块还可以轻松实现多级排序,比如上面的学生列表,先按成绩排序,再按年龄排序。

>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

6. sort 方法和 sorted 函数背后的排序算法

sorted 和 list.sort 背后的排序算法都是 Timsort 算法,它是一种自适应算法,会根据原始数据的特点交替使用归并排序(merge sort)和插入排序(insertion sort)。

在实际应用场景中这是非常有效的一种稳定排序算法。Timsort 在 2002 年开始在 CPython 中使用,2009 年起,开始在 Java 和 Android 中使用。

这里解释一下稳定排序算法,它指排序前和排序后,值相同的元素不交换位置。

举个栗子就明白了:

>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
>>> sorted(data, key=itemgetter(0))  # 按照颜色排序
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

可以看到原始序列是 (‘red’, 1) 在 (‘red’, 2)前面,排序后,因为 red 相同,二者不交换位置,(‘red’, 1) 依然在 (‘red’, 2)前面,这就叫做稳定排序。反之,如果交换了,就叫不稳定。

稳定排序非常重要,因为它可以让我们进行多步排序。
比如我们要实现对学生数据进行排序,要求是以成绩降序排列,如果成绩相同,则再按年龄升序排序。
那么我们可以通过两步实现:第 1 步是先以年龄升序排列,第 2 步再以成绩等级降序排列。注:按照算法,升序的话,B 的等级排在 A 后面。

代码如下:

>>> s = sorted(student_objects, key=attrgetter('age'))
>>> sorted(s, key=attrgetter('grade'), reverse=True)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

上面就实现了成绩降序排序,成绩相同则年龄升序排序。上面的代码我们也可以整合到一行,要注意顺序:

>>> sorted(sorted(student_objects, key=attrgetter('age')), key=attrgetter('grade'), reverse=True)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Python 大多数排序(sort 方法和 sorted 函数)用的都是 Timsort 排序算法,它是一种混和的稳定排序算法,更多关于 Timsort 排序算法的知识可以参考学习Wiki Timsort

7. sort 方法和 sorted 函数的异同

基本上学习完了,我们来小结一下 sort 和 sorted 的异同吧。

先放一下二者的官方用法

sort 的用法:

sort(self, /, *, key=None, reverse=False)
    Sort the list in ascending order and return None.

    The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
    order of two equal elements is maintained).

    If a key function is given, apply it once to each list item and sort them,
    ascending or descending, according to their function values.

    The reverse flag can be set to sort in descending order.

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.

接下来比较二者的异同。

相同点:

  1. sort 和 sorted 都有两个可选仅限关键字参数 key 和 reverse,都是默认升序排序。

不同点:

  1. sort 是列表的一个方法,它的第一个参数是 self,即列表实例对象本身;sorted 是内置函数,它的第一个参数是 iterable,即可迭代对象。所以 sorted 不止可以作用于列表,还可以作用于元组、字典等可迭代对象。
  2. sort 方法是对列表原地排序,返回值是 None;sorted 函数是返回一个新的列表,不改变原可迭代对象。
  3. sort 方法不能级联调用,sorted 函数可以级联调用。

8. 巨人的肩膀

  1. Sorting HOW TO
  2. Timsort
  3. 《Fluent Python》

后记:
我从本硕药学零基础转行计算机,自学路上,走过很多弯路,也庆幸自己喜欢记笔记,把知识点进行总结,帮助自己成功实现转行。
2020下半年进入职场,深感自己的不足,所以2021年给自己定了个计划,每日学一技,日积月累,厚积薄发。
如果你想和我一起交流学习,欢迎大家关注我的微信公众号每日学一技,扫描下方二维码或者搜索每日学一技关注。
这个公众号主要是分享和记录自己每日的技术学习,不定期整理子类分享,主要涉及 C – > Python – > Java,计算机基础知识,机器学习,职场技能等,简单说就是一句话,成长的见证!
每日学一技

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值