为什么Python3.0中的sort/sorted函数移除了cmp参数?

44

来自Python维基

在Py3.0中,完全删除了cmp参数(作为简化和统一语言的更大努力的一部分,消除了富比较和__cmp__方法之间的冲突)。

我不明白为什么在Py3.0中删除了cmp参数的原因。

考虑下面这个例子:

>>> def numeric_compare(x, y):
        return x - y
>>> sorted([5, 2, 4, 1, 3], cmp=numeric_compare)
[1, 2, 3, 4, 5]

现在考虑这个版本(建议并与3.0兼容):

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K(object):
        def __init__(self, obj, *args):
            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
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

>>> sorted([5, 2, 4, 1, 3], key=cmp_to_key(reverse_numeric))
[5, 4, 3, 2, 1]

前者非常冗长,而后者只需要一行就可以实现相同的目的。另外,我正在编写自定义类,想要编写__cmp__方法。从我在网上的一些阅读中得知,建议编写__lt__,__gt__,__eq__,__le__,__ge__,__ne__而不是__cmp__。 那么,为什么要这样建议呢?我不能简单地定义一个__cmp__方法吗?


3
您在询问两件不同的事情,一是使用__cmp__方法使类可以比较,二是使用cmp作为关键字参数来自定义排序函数。当然,它们并非完全没有关联,但它们绝不是同一件事情。当您编写一个比较对象的 cmp 函数时,它不关心是否使用 __cmp____lt__ 来进行比较;当您编写一个创建对象键值的 key 函数时,它不关心是否使用 __cmp____lt__ (或都不用)来进行比较。那么,您是在问这两个问题中的哪一个? - abarnert
实际上,有第三件事可能会让你感到困惑,即cmp函数,在3.x中也被移除了。 - abarnert
@user2708477:没错,__cmp__特殊方法从来不会被调用,排序相关的函数中也没有cmp参数,而且也没有内置的cmp函数。 - abarnert
1
@user2708477:没错。 - abarnert
这是一个关于Python及其比较函数的非常好的视频(链接:http://youtu.be/OSGv2VnC0go?t=9m42s),我已经链接了讲解比较函数部分的开始时间,但整个视频值得一看……请花点时间观看。@l4mpi: 是的,最好作为评论。 - koffein
显示剩余2条评论
2个回答

29
对于两个对象 ab__cmp__ 要求 其中一个 的结果为 a < b, a == b, 或者 a > b。但这不一定是真的:考虑到集合,很常见的情况是 三个条件都不满足,例如 {1, 2, 3}{4, 5, 6}

因此,__lt__ 等方法被引入。但这使 Python 拥有了两种不同的排序机制,这有点荒谬,所以在 Python 3 中去掉了不够灵活的那个。

实际上,你不需要实现所有六个比较方法。你可以使用 @total_ordering 装饰器 只实现 __lt____eq__

编辑:还要注意,在排序时,key 函数可能比 cmp 更有效率:在你给出的例子中,Python 可能会调用你的 Python 比较函数 O(n²) 次。但是 key 函数只需要调用 O(n) 次,如果返回值是内置类型(通常情况下都是这样),那么 O(n²) 的成对比较还可以在 C 中完成。


3
富比较方法解决了这个问题,因为__lt____gt__都可以返回False(当然,__le____ge____eq__也都返回False,只有__ne__返回True),这直接表示第一个集合既不小于,也不大于,也不等于第二个集合。 - abarnert
1
@user1988876:此外,这个答案假设您知道在Python中比较集合的含义,但我怀疑您实际上并不知道。对于集合而言,a < b 的意思是 ab 的真子集。这就很明显了,为什么您应该得到 {1, 2, 3} < {4, 5, 6} 这样的结果。 - abarnert
2
@user1988876,这就是__cmp__的问题所在:你必须选择三个可能答案中的一个,而你的第一反应是_编造_一些让你做到这一点的东西,但对于这两个集合来说,它们都不正确。(你不能通过它们的第一个元素比较集合,因为它们是无序的!) - Eevee
1
我刚刚意识到的一件事(在我看来,这可能比不相交集合之间的比较更好的例子):即使显然float('nan') < float('nan')Falsecmp(float('nan'), float('nan'))仍然产生-1 - berdario
2
我们应该接受cmp功能比key功能更灵活,即使我们可以在99%的情况下轻松地将我们的cmp转换为key,但在某些情况下我们可能无法确定地执行此操作(以不干净的方式),特别是当与C代码兼容性很重要时。虽然C仍在使用strcmp,但Python放弃了所有类型的cmp - saeedgnu
显示剩余9条评论

16
由于在大多数情况下,使用.sort()sorted()key属性通常比cmp更好。cmp其实只是在C语言中保留下来的,并且很容易造成困扰。必须要在富比较运算符(__lt____gt__等)旁边实现一个单独的__cmp__方法,这非常令人迷惑和无用。

您始终可以使用functools.cmp_to_key()来适应现有的cmp函数。

当然,对于特定的例子,可以不使用key函数来实现,例如整数已经是可排序的,只需添加reverse=True即可。

对于自定义类,请使用@functools.total_ordering装饰器将一个__eq__和一个比较运算符方法(例如__lt____gt__等)扩展为完整的排序实现。


同样地,functools.total_ordering 对于第二部分可能会有所帮助——尽管我一直认为它应该存在于一个 classtools 模块中 :) - mgilson
如果不使用“key”,就无法实现,因为它是在反向比较。 (在这种情况下,“key”可以只是“neg”或“lambda x:-x”。)如果使用“reverse”关键字,则可以不使用“key”,但需要其中之一。 - abarnert
@abarnert:啊,我是想提到“reverse”的。 - Martijn Pieters
@functools.total_ordering 看起来更像是一个类装饰器而不是函数装饰器。我以前没有使用过它,所以只是确认一下。 - brain storm
1
@user1988876:@functools.total_ordering确实是一个类装饰器。 - Martijn Pieters
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接