Python:functools cmp_to_key函数如何工作?

时间:2015-09-24 03:16:47

标签: python sorting deprecated

在Python中,list.sort方法和sorted内置函数都接受一个名为key的可选参数,该参数是一个函数,给定列表中的元素返回其排序键

较旧的Python版本使用了cmp参数的不同方法,这是一个函数,如果第一个小于第二个,则列表中的两个元素返回负数,如果有等于,则返回零如果第一个更大,则为正数。在某些时候,这个参数已被弃用,并且不包含在Python 3中。

有一天,我想以一种cmp函数比key函数更容易编写的方式对元素列表进行排序。我不想使用已弃用的功能,所以我阅读了文档,发现cmp_to_key模块中有一个名为functools的功能,正如他的名字所示,它接收到cmp 1}}函数并返回一个key一个......或者那个我想到的东西,直到我读到{{3中包含的这个高级函数的源代码(或至少一个等价版本) }}

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

尽管cmp_to_key按预期工作,但我感到惊讶的是,这个功能并没有返回一个函数而是一个K类。为什么?它是如何工作的?我猜它sorted函数在内部检查cmp是函数还是K类或类似的东西,但我不确定。

P.S。:尽管他很奇怪,但我发现K级非常有用。检查此代码:

from functools import cmp_to_key

def my_cmp(a, b):
    # some sorting comparison which is hard to express using a key function

class MyClass(cmp_to_key(my_cmp)):
    ...

这样,默认情况下,MyClass的任何实例列表都可以按my_cmp

中定义的条件排序

3 个答案:

答案 0 :(得分:15)

不,sorted函数(或list.sort)在内部不需要检查它收到的对象是函数还是类。所有它关心的是它在key参数中收到的对象应该是可调用的,并且应该返回一个值,该值可以在被调用时与其他值进行比较。

类也可以调用,当你调用一个类时,你会收到该类的实例。

要回答您的问题,首先我们需要了解(至少在基本层面)key参数的工作原理 -

  1. 为每个元素调用key callable,并接收它应该对其进行排序的对象。

  2. 收到新对象后,它与其他对象进行比较(再次通过调用其他元素调用key来接收)。

  3. 现在需要注意的重要一点是,收到的新object与其他相同的对象进行比较。

    现在使用等效代码,当您创建该类的实例时,可以使用mycmp函数将其与同一类的其他实例进行比较。排序值时排序比较这些对象(实际上)调用mycmp()函数来确定该值是否小于或大于另一个对象。

    打印报表示例 -

    >>> def cmp_to_key(mycmp):
    ...     'Convert a cmp= function into a key= function'
    ...     class K(object):
    ...         def __init__(self, obj, *args):
    ...             print('obj created with ',obj)
    ...             self.obj = obj
    ...         def __lt__(self, other):
    ...             print('comparing less than ',self.obj)
    ...             return mycmp(self.obj, other.obj) < 0
    ...         def __gt__(self, other):
    ...             print('comparing greter than ',self.obj)
    ...             return mycmp(self.obj, other.obj) > 0
    ...         def __eq__(self, other):
    ...             print('comparing equal to ',self.obj)
    ...             return mycmp(self.obj, other.obj) == 0
    ...         def __le__(self, other):
    ...             print('comparing less than equal ',self.obj)
    ...             return mycmp(self.obj, other.obj) <= 0
    ...         def __ge__(self, other):
    ...             print('comparing greater than equal',self.obj)
    ...             return mycmp(self.obj, other.obj) >= 0
    ...         def __ne__(self, other):
    ...             print('comparing not equal ',self.obj)
    ...             return mycmp(self.obj, other.obj) != 0
    ...     return K
    ...
    >>> def mycmp(a, b):
    ...     print("In Mycmp for", a, ' ', b)
    ...     if a < b:
    ...         return -1
    ...     elif a > b:
    ...         return 1
    ...     return 0
    ...
    >>> print(sorted([3,4,2,5],key=cmp_to_key(mycmp)))
    obj created with  3
    obj created with  4
    obj created with  2
    obj created with  5
    comparing less than  4
    In Mycmp for 4   3
    comparing less than  2
    In Mycmp for 2   4
    comparing less than  2
    In Mycmp for 2   4
    comparing less than  2
    In Mycmp for 2   3
    comparing less than  5
    In Mycmp for 5   3
    comparing less than  5
    In Mycmp for 5   4
    [2, 3, 4, 5]
    

答案 1 :(得分:1)

我刚才意识到,尽管不是函数,K类是可调用的,因为它是一个类!和类是callate,调用时会创建一个新实例,通过调用相应的__init__对其进行初始化,然后返回该实例。

这种方式表现为key函数,因为K在调用时接收对象,并将此对象包装在K实例中,该实例可以与其他K实例进行比较。

如果我错了,请纠正我。我觉得我正在进入我不熟悉的元级领域。

答案 2 :(得分:1)

我没有查看源代码,但我相信关键函数的结果也可以是任何东西,因此也是一个类似的对象。并且cmp_to_key只是屏蔽了那些K对象的创建,这些对象在排序工作时会相互比较。

如果我尝试在部门和反向房间号码上创建这样的排序:

departments_and_rooms = [('a', 1), ('a', 3),('b', 2)]
departments_and_rooms.sort(key=lambda vs: vs[0])
departments_and_rooms.sort(key=lambda vs: vs[1], reverse=True)
departments_and_rooms # is now [('a', 3), ('b', 2), ('a', 1)]

这不是我想要的,我认为排序只在每次通话时都是稳定的,documentation会误导imo:

  

sort()方法保证稳定。如果排序保证不更改比较相等的元素的相对顺序,则排序是稳定的 - 这有助于在多个过程中进行排序(例如,按部门排序,然后按工资等级排序)。

旧样式方法有效,因为调用K类的每个结果都返回一个K实例并与mycmp的结果进行比较:

def mycmp(a, b):                             
    return cmp((a[0], -a[1]), (b[0], -b[1]))

departments_and_rooms = [('a', 1), ('a', 3),('b', 2)]
departments_and_rooms.sort(key=cmp_to_key(mycmp))
departments_and_rooms # is now [('a', 3), ('a', 1), ('b', 2)]

这是一个重要的区别,即开箱即可完成多次传球。键函数的值/结果必须按顺序排序,而不是要排序的元素。因此是cmp_to_key掩码:创建一个需要对它们进行排序的可比较对象。

希望有所帮助。并感谢cmp_to_key代码中的洞察力,帮助我很多:))