Python | 比较器

Python 比较器

📝本文解决如下问题:

  1. 什么是比较器
  2. 为什么需要比较器
  3. python里怎么使用比较器

你也可以直接先看总结

Let's go.🏃‍♀️

1. 什么是比较器

顾名思义,用来对两个数进行比较的工具,这个工具的原理就是比较的标准,可以由生产工具的你自己定义。

一句话,定制版比较工具。

2.为什么需要比较器

既然是定制版比较工具,说明需要针对比较特殊的排序需求,以及特殊的待排序结构。

因为有时候需要自定义排序标准,所以需要可以自定义比较标准的比较器。

3. python里怎么使用比较器

本文内容假定python版本在2.4及以上

Python 列表有一个内置的 list.sort() 方法可以就地修改列表。

还有一个 sorted()内置函数,可以从一个可迭代对象构建一个新的排序列表。

这两个排序方法可以自定义比较器

list.sort()仅用于列表排序,sorted() 则可以针对任何可迭代对象,总的来说,sorted() 更方便

list.sort() 和 sorted() 都有一个key参数,用于指定要调用的函数(这个函数,在进行比较之前要先作用在列表的每个元素上)。

Example:对复杂对象根据索引排序

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]
print(sorted(student_tuples, key=lambda student: student[2]))  # sort by age
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

💡我的理解:

lambda student: student[2]分别作用于student_tuples中的每个元素,即('john', 'A', 15),('jane', 'B', 12)和

('dave', 'B', 10),得到了15,12和10,排序即针对15, 12, 10进行, 默认是升序,所以最后排序结果是

[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]。

如果需要降序,可以设置reverse参数为True

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]
# sort by age,reverse
print(sorted(student_tuples, key=lambda student: student[2], reverse=True)) 
# [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

当然,排序对象也可以是更复杂的对象,比如:

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))

student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]
print(sorted(student_objects, key=lambda student: student.age))# sort by age
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

上述的 key-function 模式非常常见,因此 Python 官方直接提供了一些方便的函数供我们直接调用。比如,operator 模块里有 itemgetter()、attrgetter() 和 methodcaller() 函数。使用这些函数,上面的例子可以更方便完成。

from operator import itemgetter, attrgetter

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))

student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]

print(sorted(student_tuples, key=itemgetter(2)))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
print(sorted(student_objects, key=attrgetter('age')))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

operator模块函数还可以进行多级排序,比如先按照成绩排序,然后成绩相同的再按照年龄排序

print(sorted(student_tuples, key=itemgetter(1,2)))
# [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

print(sorted(student_objects, key=attrgetter('grade', 'age')))
# [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

python2中支持 cmp 参数来处理用户指定的比较函数

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

需要解释的是,在使用cmp-function模式时,这里使用的函数(比如上例中的numeric_compare)需要满足一个要求:接受两个参数进行比较,然后返回一个负值表示小于,如果它们相等则返回零,或者返回一个正值表示大于

numeric_compare函数接收两个参数x和y, 返回值是$x-y$, 满足$x<y$时返回负值,相等时返回0,$x>y$时返回正值。默认升序排列,即返回负值时,默认排成x, y,返回正值排成y, x。所以上例最后实现了升序排序。

如果想实现降序则可以稍做修改:

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

python3中移除了cmp参数,只能用key参数。

所以当用户需要用自定义的比较规则时,python3中提供了functools模块的cmp_to_key()函数实现将用户自定义的比较函数映射成key-function模式。

简单点说,cmp_to_key()函数就是个包装器,将你的函数放进去之后,key参数就认识你的函数了。

包装器的样子:(其中的mycmp就是需要传进去的你自己的函数)

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K:
        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

把你自己的函数(比如reverse_numeric)转换成key-function模式:

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

当然,你还可以自己定义运算符,将你要排序的对象变成基本类型数值,然后就可以像其他基本类型比如整型数值一样直接调用sorted()进行排序,无需再指定key=function。

怎么自定义呢?

向上看cmp_to_key()函数中的class k,我们可以自己定义一个类,在里面重写下面这些方法:

__lt__: <
__gt__: >
__ge__: >=
__eq__: ==
__le__: <=
__ne__:!=

还是以上述的Student类为例,实操看看:🍻

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))
    def __lt__(self, other):
        return self.grade < other.grade if self.grade != other.grade else self.age < other.age
    def __gt__(self, other):
        return self.grade > other.grade if self.grade != other.grade else self.age > other.age
    def __eq__(self, other):
        return self.grade == other.grade and self.name == other.name and self.age == other.age
    def __le__(self, other):
        return self.grade <= other.grade if self.grade != other.grade else self.age <= other.age
    def __ge__(self, other):
        return self.grade >= other.grade if self.grade != other.grade else self.age >= other.age
    def __ne__(self, other):
        return self.grade != other.grade or self.name != other.name or self.age != other.age

student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]

print(sorted(student_objects))
# [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
print(sorted(student_objects, reverse=True))
# [('jane', 'B', 12), ('dave', 'B', 10), ('john', 'A', 15)]

总结一下

说简单点,比较器常用的方法就是下面两类:

⭐第一类:key=function

对于key=function中的function:

1️⃣你可以用lamda公式直接写

2️⃣也可以使用python官方提供的函数(比如上述的itemgetter()等函数)

3️⃣还可以自己定义一个函数,再用cmp_to_key()包装一下

⭐第二类:自定义运算符

在类中重写比较函数,然后直接用常规的比较运算来比较两个值的大小。

posted @ 2022-08-12 10:32  万国码aaa  阅读(571)  评论(0编辑  收藏  举报