Python 内置函数之排序 sort

list.sort() 函数

描述

sort() 函数用于对原列表进行排序,如果指定参数,则使用指定的比较函数。

语法

1
2
3
4
5
list.sort(key=None, reverse=False)

参数:
key:主要是用来指定进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse:排序规则,reverse = True 降序, reverse = False 升序(默认)。

注意:函数无返回值,只能对列表进行排序

示例:

1
2
3
l = [1, 5, 6, 3, 2]
l.sort(reverse=True) # 降序
print(l) # [1, 2, 3, 5, 6]

sorted() 函数

描述

Python 内置的 sorted() 函数,用于对列表进行排序。使用该函数进行排序后,原列表的元素顺序不变。

语法

1
2
3
4
5
6
# Python2 中
sorted(iterable, cmp=None, key=None, reverse=False)


# Python3 中
sorted(iterable,key=None,reverse=False)

Python3 相比于 Python2 废弃了 cmp 参数,这个下文会说。

返回值: 返回一个新的list

reverse 参数

可指定参数 reverse 来表示升序或降序True 表示降序,Fasle 表示升序。默认是升序。

1
2
3
4
# 升序排序
sorted([5, 2, 3, 1, 4]) # [1, 2, 3, 4, 5]
# 降序排序
sorted([5, 2, 3, 1, 4], reverse=True) # [5, 4, 3, 2, 1]

key 参数

从 python2.4 开始,list.sort()sorted() 函数都增加了 key 参数来指定一个函数此函数将在每个元素比较前被调用。 默认值为 None。例如通过 key 指定的函数来忽略字符串的大小写:

1
sorted("A d c E b".split(), key=str.lower)  # ['A', 'b', 'c', 'd', 'E']

key参数的值为一个函数,此函数只有一个参数且返回一个值用来进行比较。

更广泛的使用情况是用复杂对象的某些值来对复杂对象的序列排序,例如:

1
2
3
4
5
6
7
8
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)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

cmp 参数与 functools.cmp_to_key() 函数

Python 2

Python2中,可以使用cmp参数。cmp 指定一个 定制的比较函数,这个函数接收两个参数(iterable的元素),如果第一个参数小于第二个参数,返回一个负数;如果第一个参数等于第二个参数,返回零;如果第一个参数大于第二个参数,返回一个正数。cmp的默认值为None。

  • 如果想要升序排序,就要:return 1 if val1 > val2 else -1 或写成 return val1 - val2
  • 如果想要降序排序,就要 return 1 if val1 < val2 else -1 或写成 return val2 - val1

注意,有没有发现上面的第一种跟其他语言中的不太一样,比如在C++中,一般写法是这样的:

1
2
3
4
bool cmp(int a, int b){
return a > b; // 降序
return a < b; // 升序
}

是不是直观感觉是反的,这是为什么呢?

这是因为cmp中定义的规则就相当于告诉sorted()如何对两个数进行比较,让它清楚什么样的条件是“大”什么样的条件是“小”

注意: 定义这个排序规则只是为了让sorted()明白如何得到两个数中较“大”的那个与数组最后是“从小到大”排序还是“从大到小”排序没有关系

此外,C++中是可以返回布尔值的,但Python中就不可以这样写,比如下面的写法就是错误的,会导致无效排序:

1
2
def cmp(a, b):
return a > b

这里因为在 Python 中,True 的值为1, 而 False 的值为0

举个例子:

1
2
3
4
5
6
7
8
9
10
11
# 字典按 key 降序排序,再按 val 升序排序
dic = {"a": 4, "c": 3, "b": 2, "d": 2}

def cmp_func(val1, val2):
if val1[0] != val1[0]:
return 1 if val1[0] < val2[0] else -1
else:
return 1 if val1[1] > val2[0] else -1

dic = sorted(dic.items(), cmp=cmp_func)
print(dic) # [('d', 2), ('c', 3), ('b', 2), ('a', 4)]

Python 3

Python3 废弃了 Python2 中 sorted 函数的 cmp 参数。 所以我们只能使用 key 参数进行排序,如果非要使用cmp函数进行自定义排序的话,可以借助 functools 模块中的 cmp_to_key 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# python3 的 sorted 函数,没有 cmp 参数,只能通过 cmp_to_key 传给 key,实现 python2 中 sorted
from functools import cmp_to_key


d = {"a": 4, "c": 3, "b": 2, "d": 2}

def cmp_func(val1, val2):
if val1[0] != val1[0]:
return 1 if val1[0] < val2[0] else -1
else:
return 1 if val1[1] > val2[0] else -1


dic = sorted(d.items(), key=cmp_to_key(cmp_func))
print(dic) # [('d', 2), ('c', 3), ('b', 2), ('a', 4)]

Operator 模块函数

面的 key 参数的使用非常广泛,因此 Python 提供了一些方便的函数来使得访问方法更加容易和快速。operator模块itemgetterattrgetter,从2.6开始还增加了methodcaller方法。使用这些方法,上面的操作将变得更加简洁和快速:

1
2
3
4
5
6
7
8
9
10
from operator import itemgetter, attrgetter
student_tuples = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
sorted(student_tuples, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

operator模块还允许多级的排序,例如,先以grade,然后再以age来排序:

1
2
3
4
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)]

sort 与 sorted 区别

  1. sort() 是应用在 list 上的方法,sorted() 可以对所有 可迭代的对象 进行排序操作。
  2. listsort() 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted() 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

排序的稳定性

从 Python2.2 开始,排序被保证为稳定的。意思是说多个元素如果有相同的key,则排序前后他们的先后顺序不变

1
2
data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
sorted(data, key=lambda x: x[0]) # [('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

Python:自定义比较运算符

  1. object 类中定义了 __lt__()__le__()__eq__()__ne__()__gt__()__ge__() 等方法,这组方法定义了对象之间可以使用 <<==!=>>= 比较结果,也就是 python 的Rich comprison方法(富比较或厚比较)。但由于object定义这些方法的局限性,实际使用时我们往往需要重新定义这些方法。例如:object定义的__eq__()is两比较两个对象是否同一对象,并不是实质的相等性。

  2. __ne__()会默认调用__eq__(),并对结果求反,因此定义了__eq__()就相当于定义了 __ne__()

  3. 由于__lt__()__ge__()互补,__le__()__gt__()互补,故只需要定义__gt__()__ge__()

  4. 并不是每个对象都需要定义整组比较方法。如果真需要定义整组方法的行为,可以使用functoolstotal_ordering。当一个类被标注了@total_ordering时,必须实现__eq__(),并选择__lt__()__le__()__gt__()__ge__()其中一个方法实现

举例:

有一个Number类,它有两个属性:val1val2 需要按以下规则比较对象的大小:

  1. 首先按照val2的值进行比较,val2的值越大,其对象大小越大;
  2. val2的值相同时,val1的值越大,其对象大小越大
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
#普通方法定义,定义eq、gt、ge
class Number:
def __init__(self, val1, val2):
self.val1 = val1
self.val2 = val2

def __eq__(self, other):
if self is other:
return True
if hasattr(other, 'val1') and hasattr(other, 'val2'):
return self.val1 == other.val1 and self.val2 == other.val2

def __gt__(self, other):
if self.val2 == other.val2:
return self.val1 > other.val1
else:
return self.val2 > other.val2

def __ge__(self, other):
if self.val2 == other.val2:
return self.val1 >= other.val1
else:
return self.val2 > other.val2

number1 = Number(10, 99)
number2 = Number(10, 100)
number3 = Number(11, 99)
print(number1 > number2)
'False'
print(number3 > number1)
'True'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import total_ordering

@total_ordering #total_ordering定义
class Number:
def __init__(self, val1, val2):
self.val1 = val1
self.val2 = val2

def __eq__(self, other):
if self is other:
return True
if hasattr(other, 'val1') and hasattr(other, 'val2'):
return self.val1 == other.val1 and self.val2 == other.val2

def __gt__(self, other):
if self.val2 == other.val2:
return self.val1 > other.val1
else:
return self.val2 > other.val2

重新回到排序

所以,如果使用了自定义比较运算符,那么将对象排序时就无需再使用key参数cmp参数了。

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
num_list = []
num_list.append(Number(10, 99))
num_list.append(Number(6, 99))
num_list.append(Number(3, 100))
num_list.append(Number(6, 100))

num_list.sort() # 升序排序
for i in range(4):
print(num_list[i].val1, num_list[i].val2)

# 输出
# 6 99
# 10 99
# 3 100
# 6 100

num_list.sort(reverse=True) # 降序排序
for i in range(4):
print(num_list[i].val1, num_list[i].val2)

# 输出
# 6 100
# 3 100
# 10 99
# 6 99

Reference


Python 内置函数之排序 sort
https://flepeng.github.io/021-Python-31-Python-内置函数-Python-内置函数之排序-sort/
作者
Lepeng
发布于
2021年3月17日
许可协议