专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Python3 对象 cmp_to_key 自定义排序

在 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 重与很多其他语言一样,sortedsort 都提供了一个 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]

查看 functoolscmp_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 这个参数的目的。

文章永久链接:https://tech.souyunku.com/45716

未经允许不得转载:搜云库技术团队 » Python3 对象 cmp_to_key 自定义排序

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们