在Python中如何使用sorted()和sort()函数
在某种程度上,所有的程序员都必须编写代码对项目或数据进行排序。在应用程序当中,排序对于用户体验而言是非常重要的,无论是按时间戳对用户最近的活动进行排序,还是按照姓氏的字母顺序排列电子邮件收件人列表。Python的排序功能十分强大,可以在粒度级别上进行基本排序或自定义排序。
在本教程中,你将会学习如何在不同的数据结构中对各种各样的数据类型进行排序和自定义排序,并使用Python中两种不同的排序方法。
在这篇教程结束时,你将会知道如何:
1、在数据结构上完成基本的Python排序
2、区分sorted()和.sort()函数
3、基于特定的要求在编码中自定义一个复杂的排序
对于本篇教程而言,你需要对列表、元组以及集合有一个基础的理解。在本篇教程中,将会使用到这些数据结构,并且在这些数据结构上将会进行一些基础的操作。而且,这篇教程使用的是Python3的版本,所以如果你使用的是Python2的版本,输出样例可能会稍微有些差别。
使用sorted()函数排序
在开始使用Python进行排序之前,首先你要了解如何对数值和字符串数据进行排序。
对数值进行排序
你可以使用Python中的sorted()函数对一个列表进行排序。在本例中,定义了一个整数列表,然后调用sorted()函数,可变的numbers作为sorted()函数的参数。
输出结果是一个新的,有序的列表。当打印原始变量时,我们可以知道初始值并没有发生改变。
这个例子展示了sorted()函数四种重要的特性:
1.sorted()函数不需要定义。它是一个内置函数,可以在标准的Python安装中使用。
2.在没有额外的参数的情况下,sorted()函数按照升序对值进行排列,也就是按照从小到大的顺序。
3.原始的numbers不会改变,因为sorted()函数提供了一个新的有序的输出结果,并且不改变原始值的顺序。
4.当sorted()函数被调用时,它会提供一个有序的列表作为返回值。
最后一点意味着列表可以使用sorted()函数,并且输出结果可以立刻赋值给一个变量:
在这个例子中,有一个新的变量numbers_sorted存储了sorted()函数的输出结果。
你可以通过调用help()函数来查看sorted()函数以确认所有的这些观察结果。可选参数key和reverse将在本教程后面介绍:
技术细节:如果你正在从Python2过渡到Python3,并且对它的同名函数非常熟悉,你应该注意Python3中的几个重要变化:
1.Python3中的sorted()函数没有cmp参数。相反,只使用key参数来引入自定义排序逻辑。
2.key和reverse必须作为参数传递,这与Python2不同,在Python2中它们可以作为位置参数传递。
如果需要将Python2的cmp函数转换为key函数,请查看functools.cmp_to_key()。本教程将不介绍使用Python2的任何示例。
元组和集合同样可以使用sorted()函数:
值得注意的是即使输入的是一个集合和元组,输出结果仍然是一个列表,因为sorted()函数根据定义会返回一个新列表。如果返回的对象需要匹配输入类型,则可以将其转化为新类型。如果试图将结果列表转换回集合类型,请注意,按照定义而言,集合是无序的:
正如预料的结果一样,当把结果列表转换为集合时,numbers_set_sorted是无序的。其它的变量,如numbers_tuple_sorted保留了排序后的顺序。
对字符串进行排序
str类型的排序类似于列表和元组等其它可迭代对象。下面的例子展示了sorted()函数如何遍历传递给它的值中的每个字符并在输出中对字符进行排序:
sorted()函数将一个str看作一个列表,并遍历其中的每一个元素。在一个str中,每一个元素都对应着str中的一个字符。sorted()函数以相同的方式对待每一个句子,它会对每个字符包括空格进行排序。
.split()可以改变这个结果并清理输出,.join()可以将所有内容重新连接在一起。我们将会简单介绍输出的特定顺序以及为什么是这样:
在本例中,原句被转换为一个单词列表而不是作为一个str。然后,对该列表进行排序并再次组合形成一个str而不是一个列表。
Python排序的局限性和陷阱
值得注意的是,当你使用Python对非整数类型的值进行排序时,可能会出现一些限制和奇怪的结果。
含有不可比较数据类型的列表无法使用sorted()函数
有些数据类型不能使用sorted()函数相互比较,因为它们太不一样了。如果你试图对一个含有不可比较数据类型的列表使用sorted()函数,Python将会返回一个错误。在本例当中,同一个列表中的None和int不能排序,因为它们是不兼容的:
这个错误说明了为什么Python不能对给定的值进行排序。它试图通过使用小于操作符(<)来确定哪个值更低,从而将值按顺序排列。
当你试图在不使用sorted()函数的情况下比较两个不可比较值时,Python会抛出相同的类型错误。
如果列表中的值可以比较并且不会抛出类型错误,那么这个列表就可以进行排序。这可以防止对具有本质上不可排序值的迭代器进行排序,并生成可能没有意义的输出。
例如,数字1应该放在单词apple之前吗?然而,如果一个iterable包含整数和字符串的组合,并且它们都是数字,那么可以使用列表将它们转换为可比较的数据类型:
mixed_numbers中的每一个元素都调用了int()函数,可以将任何str值转化为int值。然后,调用sorted()函数就可以成功比较每一个元素,并产生一个排序的结果。
Python还可以隐式地将值转换为另一种类型。在下面的例子中,1<=0的值为False,所以输出结果就是False。作为布尔类型时,数字1可以转化为True,数字0可以转化为False。
即使列表中的元素看起来都不相同,但是它们都可以转化为布尔值(True或False),并使用sorted()函数相互比较:
'A'=='B'和1<=0都被转化为了布尔值False,并且返回了一个有序的输出结果。
这个例子说明了一个排序的重要方面:排序稳定性。在Python当中,当你对相等的值进行排序时,它们将会在输出时保留原本的顺序。即使移动了1的位置,所有其他的值都是相等的,因此它们保持了相对于彼此的原始顺序。在下面的例子中,所有的值都是相等的,并且将会保持它们原本的位置:
如果你观察原始的顺序和输出的顺序,你将会发现1 == 2被转化成了False,并且所有排序后的输出都与原始顺序相同。
当你对字符串排序时,大小写很重要
sorted()函数可以按照升序对字符串列表的值进行排序,默认情况下按照字母顺序:
然而,Python使用每个字符串中第一个字母的Unicode数值来确定升序排序顺序。这意味着sorted()函数不会将名称AL和al看作是一样的。本例将会使用ord()函数返回每个字符串中第一个字母的Unicode数值:
name[0]会返回sorted(names_with_case)中每一个元素的第一个字符,ord()会提供其Unicode数值。即使在字母表中a在M之前,但是M的Unicode数值在a之前,所以排序的结果是M在前。
如果第一个字母是相同的,那么sorted()将使用第二个字符来确定顺序,如果第二个字符是相同的,将会使用第三个字符,以此类推,直到字符串的末尾:
除了最后一个字符外,very_similar_strs的每一个字符都是相同的。使用sorted()函数比较字符串时,由于前五个字符都是相同的,所以输出结果将会根据第六个字符的值来判断。
包含相同值的字符串最终的顺序为从短到长,这是由于较短的字符串没有可以与较长字符串相比较的元素:
最短的字符串'h'在第一位,最长的字符串'hhhhh'在最后一位。
使用含有reverse参数的sorted()函数
正如sorted()函数的help()文档所示,有一个可选的参数reverse,它将根据分配给它的布尔值改变排序。如果reverse = True,那么就会按照降序排列:
排序的逻辑仍然保持不变,这意味着这些名字仍然按照第一个字母排序,但是因为reverse关键字被设置为True,所以输出结果的顺序是相反的。
当reverse关键字设置为False时,顺序将保持升序。之前的任何例子都可以用来检查reverse关键字设置True或Fasle后的结果:
使用含有key参数的sorted()函数
参数key是sorted()函数最强大的组成部分之一。这个参数可以接收一个函数,该函数将作用于排序列表中的每个值,以确定结果的顺序。
以一个简单的例子为例,对一个特定的列表进行排序,我们假设列表中字符串的长度为排序的要求,由短到长。参数key被设置为len()函数,len()函数的功能是返回一个字符串的长度:
最后的结果是一个按字符串顺序从短到长的列表。列表中每个元素的长度是由len()函数确定的,然后按照升序返回。
让我们回到前面的例子,按照第一个字母排序,当出现大小写不同的情况时,key可以通过将整个字符串转换成小写来解决这个问题:
输出值没有被转化为小写,这是因为参数key并没有处理原始列表中的数据。在排序过程中,将对每个元素调用key函数来确定排序顺序,但是输出的仍是原始值。
当使用带有参数key的函数时,有两个主要限制。
第一,传递给key的函数中所需参数的数量必须为1。
下面的例子说明了加法函数的定义,它需要两个参数。当加法函数作为参数key作于用一个数值列表时,它并不能发挥作用,因为它缺少第二个参数。在排序过程中每次调用add()函数,它每次只从列表中接收一个元素:
第二个限制是,带有key的函数必须能够处理迭代序列中的所有值。例如,你有一个以字符串形式表示的数值列表,要对其使用sorted()函数,参数key试图使用int()函数将它们转化为数字。如果迭代序列中的值不能转换为整数,那么该函数将无法产生作用:
每个作为str的数值都可以转换为int,但是four不行。这会引起一个ValueError,其错误解释为four不能被转化为整形,因为它是无效的。
参数key的功能非常强大,因为几乎所有函数,无论是内置函数还是用户自定义函数,都可以用来控制输出顺序。
如果排序要求是按照每个字符串的最后一个字母对可迭代序列进行排序(如果最后一个字母是相同的,就使用倒数第二个字母),那么就可以定义一个函数用来排序。下面的例子定义了一个函数,其功能是反转字符串序列,然后将该函数作为参数传递给key:
word[::-1]用于反转字符串。reverse_word()将会作用于每一个元素,而且排列顺序将会取决于最后的字符。
你可以在参数中定义lambda函数来代替编写一个独立的函数。
lambda函数是一个匿名函数:
1.必须是内联定义
2.没有名称
3.不能包含语句
4.像函数一样执行
在下面的例子中,参数key被设置为一个没有名称的lambda函数,lambda的参数是x,x[::-1]是对参数执行的操作:
对每个元素调用x[::-1]并反转单词。反转后的单词被用于排序,但是返回的仍然是原始的单词。
如果需求发生了变化,并且顺序也应该颠倒,那么reverse关键字可以和key参数一起使用:
当你需要根据属性对类对象排序时,lambda函数也很有用。如果你有一组学生,需要根据他们的最终成绩按照从高到低的顺序对他们进行排序,那么lambda可以用来从类中获取grade属性:
lambda在每个元素上调用getattr()函数并返回grade的值。
将reverse设置为True,使升序输出变为为降序输出,以便使成绩最高的排在第一位。
当你同时使用sorted()函数中的key和reverse参数时,如何实现排序的可能性是无穷无尽的。当你为一个小函数使用基本lambda式时,代码可以保持简短和整洁,或者你可以编写一个全新的函数,导入它,并在key参数中使用它。
使用.sort()对值排序
名称非常相似的.sort()与内置的sorted()有很大的差别。它们或多或少得完成了相同的事情,但是list.sort()的help()文档强调了二者之间最重要的两个区别:
第一,sort是list类的一个方法,只能与list一起使用。它不是一个内置的迭代器。
第二,.sort()返回None并改变值的位置。让我们看一下这两种代码差异的影响:
在这个代码示例中,.sort()与sorted()的操作方式有一些非常显著的差异:
1..sort()没有有序的输出,因此对新变量的赋值只传递None类型
2.values_to_sort列表的顺序已经发生了改变,而且原始顺序也并没有以任何形式保留下来。
这些行为上的差异使得.sort()和sorted()在代码中绝对不可互换,如果以错误的方式使用它们,可能会产生意想不到的结果。
.sort()具有与sorted()相同的key和reverse这种可选的关键字参数,这些参数具有与sorted()相同的强大的功能。在这里,你可以根据第三个单词的第二个字母对短语列表进行排序,然后逆序返回列表:
在本例当中,lambda函数被用来完成以下功能:
1.把每个短语分成一个单词列表
2.找到本例中的第三个元素或单词
3.找到第三单词中的第二个字母
何时使用sorted()和.sort()
你已经看到了sorted()和.sort()之间的区别,但是什么时候该用哪一个呢?
让我来说一下,有一个5k比赛即将到来:第一届年度Python 5k。需要捕获并排序来自比赛的数据。需要捕获的数据是跑步者的号码和完成比赛所需的秒数:
当参赛者跨过终点线时,每一个Runner都会被加入一个名为runners的列表当中。在5k比赛中,并不是所有的运动员都同时跨过起跑线,所以第一个越过终点线的人可能并不是最快的:
每一次一个跑步者跨过终点线,他们的号码以及耗时(以秒为单位)都会被加入到runners列表当中。
现在,负责处理结果数据的程序员看到了这个列表,知道了前5名最快的选手是获奖者,其余的参赛者将按时间排序。
不需要根据不同的属性进行多种类型的排序。这个列表的大小是合理的。没有提到将列表存储在某个地方。只需要按时间排序,找出耗时最短的5名选手:
编程人员选择在参数key上使用lambda函数,以便从每一个runner中获取它们的持续时间属性,并且使用.sort()对runners列表进行排序。在runners列表完成排序之后,前5个元素被存储在top_five_runners列表中。
任务完成!比赛总监过来告诉程序员,由于Python的当前版本是3.7,所以他们决定每37名冲过终点线的人将获得一个免费的健身包。
此时,程序员开始感到很苦恼,因为runners列表已经不可逆转地更改了,没有办法恢复原始的runners列表的顺序,并找到每37人。
如果您正在处理重要的数据,即使这些原始数据需要恢复的可能性很小,那么.sort()也不是最佳选项。如果数据是副本,如果它是不重要的工作数据,如果没有人介意丢失它,因为它可以被找回,那么.sort()是一个不错的选择。
或者,runners列表可以使用sorted()函数排序,并且使用相同的lambda表达式:
在使用sorted()函数的这个方案中,原始的runners列表仍然是完整的,没有被覆盖。通过与原始值的交互,可以实现每隔37人到达终点线的临时性要求:
every_thirtyseventh_runners列表是对runners列表使用列表切片语法创建的,它仍然包含跑步者越过终点线的原始顺序。
怎样使用Python排序:结论
.sort()和sorted()可以提供所需的排序顺序,如果你正确地将它们与可选的关键字参数reverse和key一起使用的话。
当涉及到输出和修改数据时,两者具有非常不同的特性,因此请确保您仔细考虑过将使用.sort()的任何应用功能或程序,因为它会不可逆转地覆盖数据。
对于热心寻找排序方面的挑战的Python高手来说,可以尝试在排序中使用更复杂的数据类型:嵌套迭代器。
英文原文:https://realpython.com/python-sort/
译者:Lyx