Python进阶编程:编写更高效、优雅的Python代码
上QQ阅读APP看书,第一时间看更新

3.7 排序不支持原生比较的对象

在Python中,并非所有的对象都支持原生的比较操作。但在实际应用中,我们需要对不支持原生比较的对象执行排序操作。

Python内置的sorted()函数有一个关键字参数key,其可以传入一个callable对象。callable对象对每个传入的对象返回一个值,这个返回值是sorted()函数排序传入的对象后得到的。

如果应用程序中有一个User实例序列,我们希望通过它们的user_id属性进行排序,这时可以提供一个以User实例为输入并输出对应user_id值的callable对象,示例如下:


class User(object):
    def __init__(self, user_id):
        self.user_id = user_id

    def __repr__(self):
        return 'User({})'.format(self.user_id)

if __name__ == "__main__":
    users = [User(23), User(3), User(99)]
    print(users)
    print(sorted(users, key=lambda u: u.user_id))

对于该示例,我们也可以通过operator.attrgetter()函数代替lambda()函数,示例如下:


from operator import attrgetter
    print(sorted(users, key=attrgetter('user_id')))

选择使用lambda()函数还是attrgetter()函数取决于个人喜好。不过,attrgetter()函数通常会运行得快点,并且同时允许多个字段进行比较。这与operator.itemgetter()函数作用于字典类型很类似。

如果User实例还有一个first_name和last_name属性,那么可以像下面这样排序:


by_name = sorted(users, key=attrgetter('last_name', 'first_name'))

这里用到的技术同样适用于像min()和max()之类的函数,示例如下:


print(min(users, key=attrgetter('user_id')))
print(max(users, key=attrgetter('user_id')))

扩展:按需选择sort()或者sorted()函数。

各种排序算法以及它们的时间复杂度分析是很多企业面试人员经常会问的问题,这是因为在实际应用中确实会遇到各种需要排序的情况。Python中常用的排序函数有sort()和sorted()。这两个函数并不完全相同,各有各的使用方式。两者形式如下:

1)相比于sort()函数,sorted()函数使用范围更为广泛。这两个函数有3个共同参数,即cmp、key和reverse。cmp为用户定义的比较函数,其参数为两个可比较的元素,可根据第一个参数与第二个参数的关系依次返回-1、0或1,默认参数为None;key是一个函数形式的参数,用来为每个元素提取比较值,默认值为None;reverse标识排序结果是否反转。

2)当排序对象为列表的时候,两者适用的场景不同。sorted()函数会返回一个排序后的列表,原有列表保持不变;sort()函数会直接修改原有列表,返回为None。若应用中需要保留原有列表,sorted()函数较为实用,否则选择sort()函数。对于sort()函数,由于其不需要复制原有列表,所以消耗内存较少,效率也较高。

3)不论是sort()函数还是sorted()函数,传入参数key比传入参数cmp效率要高。cmp传入的函数在整个排序过程中会调用多次,函数开销较大;而key针对每个元素仅做一次处理,因此使用key比使用cmp效率要高。