Python3 sort 自定义比较逻辑:lambda函数、可比较类、cmp_to_key

以字符串排序为例,现在我们有这样一个字符串的 list :

words = ["vanilla", "tom", "sue", "david", "bill", "tom", "jezebel", "jackb", "jack", "jacka"]

默认排序方式

对于字符串而言,其默认的比较大小逻辑为:按首个字符顺序比较大小,相同则看后一个字符,后一个字符为空者较小。

def test1(things):
things.sort()
print(things)
test1(words.copy())
输出:
['bill', 'david', 'jack', 'jacka', 'jackb', 'jezebel', 'sue', 'tom', 'tom', 'vanilla']

lambda函数

sort

方法可以接收

key

参数,传递一个如下的可调用的对象:

以下例子通过 lambda 函数设置新的排序逻辑:以字符串长度为基准。

lambda 函数需要:1.接收1个参数;2.返回一个值,且返回值是可比较大小的。

def test2(things):
things.sort(key=lambda e: len(e))
print(things)
test2(words.copy())
输出:
['tom', 'sue', 'tom', 'bill', 'jack', 'david', 'jackb', 'jacka', 'vanilla', 'jezebel']

自定义可比较类

但是 lambda 函数无法表示比较复杂的排序逻辑。比如我们想:优先按照字符串长度为基准排序,再按照字母大小排序。lambda 函数是做不到如此复杂的逻辑的。

实际上,

key

参数也可以传接收1个参数以初始化的类,比如下面的继承

str

类的

Cmp

类(因为

str

本身就接收一个参数以初始化,所以无需重写

Cmp

__init__

函数)

为了使

Cmp

是可比较大小的,需定义用于比较的 magic method,这些函数为:

__gt__

,

__lt__

,

__ge__

,

__le__

,

__eq__

,分别在进行 >, =, <=, == 运算时被调用,一个鲁棒的可比较类需要包含全部这五个函数的定义。

Cmp

中只重写了

__lt__

,因为在 sort 的执行中,优先进行 < 运算(只有在 < 运算抛出异常的情况下,才会试图进行其它比较运算)

def test3(things):
class Cmp(str):
def __lt__(self, other):
if len(self) == len(other):
return str.__lt__(self, other)
else:
return len(self) < len(other)
things.sort(key=Cmp)
print(things)
test3(words.copy())

输出:

['sue', 'tom', 'tom', 'bill', 'jack', 'david', 'jacka', 'jackb', 'jezebel', 'vanilla']

通过cmp_to_key自定义比较逻辑

定义鲁棒的

Cmp

类比较麻烦,毕竟两个对象的大小比较,无外乎“大于”、“小于”、“等于”三个状态。最直接的方法难道不是定义一个函数,输入两个参数,输出前者与后者之间的大小关系吗?可是有这样的接口吗?有的。

通过

functools

模块里的

cmp_to_key

函数,可以简洁地将上述自定义函数转化成

key

可接收的格式。

自定义函数需要:1.接收两个参数

p1

,

p2

;2.返回1、0或-1,其中1代表

p1 > p2

,0代表

p1 == p2

, -1代表

p1 < p2

def test4(things):
def compare(s1, s2):
if len(s1) == len(s2):
for c1, c2 in zip(s1, s2):
if c1 > c2:
return 1
elif c1 < c2:
return -1
return 0 # 这里的比较其实可以直接使用字符串之间的 >, 
else:
if len(s1) > len(s2):
return 1
else:
return -1
from functools import cmp_to_key
things.sort(key=cmp_to_key(compare))
print(things)
test4(words.copy())
输出:
['sue', 'tom', 'tom', 'bill', 'jack', 'david', 'jacka', 'jackb', 'jezebel', 'vanilla']