在 Python 中如果要对一个 list 进行排序通常会使用 Iterable.sort()
或者 sorted(Iterable)
,例如:
>>> sorted([2,5,4,3])
[2, 3, 4, 5]
上面只是对简单的整数对象进行排序,对于稍微复杂一点的对象排序需要用到其他参数。例如下面将用户按照年龄进行排序,就需要用到 key
这个参数:
>>> class User:
... '''用户对象'''
... def __init__(self, name, age):
... self.name = name # 用户名字
... self.age = age # 用户年龄
...
... def __repr__(self):
... return f'{self.name}: {self.age}'
>>>
>>> sorted(
... [User('user1', 20), User('user2', 10), User('user3', 15)],
... key = lambda x: x.age
... )
[user2: 10, user3: 15, user1: 20]
这个例子使用了匿名函数 lambda x: x.age
将排序的 key 设置成了对象的 age 参数,这样对 User 排序的时候就会通过比较 User 的 age 来排序。
如果再增加一个参数:用户ID,User 的定义变为如下:
>>> class User:
... '''用户对象'''
... def __init__(self, name, age, uid):
... self.name = name # 用户名字
... self.age = age # 用户年龄
... self.uid = uid # 用户 ID
...
... def __repr__(self):
... return f'{self.uid}-{self.name}: {self.age}'
要求优先根据年龄排序,年龄相同时根据 ID 排序,这样 key 这个参数就很难满足需求了。
Python2 重与很多其他语言一样,sorted
和 sort
都提供了一个 cmp
参数,用来定义对象之间的比较函数,如果要实现上面的需求,可以这样写:
>>> def user_compare(u1, u2):
... if u1.age == u2.age:
... return u1.uid - u2.uid
... return u1.age - u2.age
>>> sorted(
... [User('user1', 20,2), User('user2', 10,3), User('user3', 10, 1)],
... cmp = user_compare
... )
[1-user3: 10, 3-user2: 10, 2-user1: 20]
这里定义了一个 user_compare
函数,并作为 cmp
参数传给了 sorted
,告知 sorted
在 age 参数相同时按 uid 进行排序,age 不同时则直接按照 age 排序。
但 Python3 为了语言的简洁性去掉了 cmp
这个参数,在 Python3 重自定义比较函数需要通过 cmp_to_key
将比较函数转化成 key
可以接受的参数:
>>> from functools import cmp_to_key
>>> sorted(
... [User('user1', 20,2), User('user2', 10,3), User('user3', 10, 1)],
... key = cmp_to_key(user_compare)
... )
[1-user3: 10, 3-user2: 10, 2-user1: 20]
查看 functools
中 cmp_to_key
的实现如下:
################################################################################
### cmp_to_key() function converter
################################################################################
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K
可以看到 cmp_to_key
实际上是将比较函数 user_compare
封装成了一个 class K
,将 class User
之间的比较转换成了 class K
之间的比较。 K 在初始化时将 User 对象赋值给 self.obj
,然后实现了对象之间比较时必要的 magic method。
例如比较 K 类的两个对象 k1 和 k2 时,如果使用 k1 < k2
这个表达式进行比较,就会调用 __lt__
这个方法,进而执行 return mycmp(self.obj, other.obj)
,本质上是使用 user_compare
函数比较 k1 和 k2 中对应的 User 对象。
而 user_compare
是优先比较 age,然后比较 uid,所以最终达到了使用 key 参数实现 Python2 中 cmp 这个参数的目的。