在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
答案 0 :(得分:15)
不,sorted
函数(或list.sort
)在内部不需要检查它收到的对象是函数还是类。所有它关心的是它在key
参数中收到的对象应该是可调用的,并且应该返回一个值,该值可以在被调用时与其他值进行比较。
类也可以调用,当你调用一个类时,你会收到该类的实例。
要回答您的问题,首先我们需要了解(至少在基本层面)key
参数的工作原理 -
为每个元素调用key
callable,并接收它应该对其进行排序的对象。
收到新对象后,它将与其他对象进行比较(再次通过调用其他元素调用key
来接收)。
现在需要注意的重要一点是,收到的新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代码中的洞察力,帮助我很多:))