Python序列操作之进阶篇
简介
Python的序列(sequence)通常指一个可迭代的容器,容器中可以存放任意类型的元素。列表和元组这两种数据类型是最常被用到的序列,python内建序列有六种,除了刚刚有说过的两种类型之外,还有字符串、Unicode字符串、buffer对像和最后一种xrange对像,这几种都是不常使用的。本文讲解了列表推导式、切片命名、列表元素排序、列表元素分组的使用方法。学习了Python基本的列表操作后,学习这些进阶的操作,让我们写出的代码更加优雅简洁和pythonic。
列表推导式
当我们想要根据某些规则来构造一个列表时,首先想到的应该是列表推导式。列表推导式简化了循环操作,例如我们想要从一个原始文件名列表中获取全部.py文件,在没有列表推导式的情况下,我们通常会这样做:
file_list=['foo.py','bar.txt','spam.py','animal.png','test.py'] py_list=[] forfileinfile_list: iffile.endswith('.py'): py_list.append(file) print(py_list) #output ['foo.py','spam.py','test.py']
而如果使用列表推导式则可简化为:
py_list=[fforfinfile_listiff.endswith('.py')] print(py_list) #output ['foo.py','spam.py','test.py']
列表推导式的介绍网上资源很多,不再赘述。这里只强调,当你需要根据某个规则来构造一个列表时,首先应该想一想,能否使用简洁的列表推导式来实现该需求,否则再回到常规的方式。
为切片命名
Python的列表切片使用起来非常方便,但有时也会影响代码可读性。例如有一个字符串:
record='..........19.6..........100..........'
19.6为产品价格,100为产品数量,那么计算总价格为:
但是如果这样写,可能过一段时间我们再来读代码时已经忘记了record[10:14]、record[24:27]切出来的究竟是什么?为了解决上述问题,可以给切片命个名来增强可读性。
record='..........19.6..........100..........' price=slice(10,14) count=slice(24,27) total_price=float(record[price])*int(record[count])
slice接收的参数格式为slice(stop)、slice(start,stop[,step])。如果只接收了一个参数,则等价于切片语法[:stop],如果接收两个参数,则等价于切片语法[start:stop],如果接收三个参数,则等价于切片语法[start:stop:step]。
排序
排序相关的任务通常由内置函数sorted完成。需要排序的元素一般存放在一个列表容器中,列表可以存放任意类型的元素,而sorted函数的key关键字使得我们能够轻松地指定元素排序的关键字,让排序变得异常简单。下面将给出几个常见的排序例子以说明key关键字的使用方法。注意Python3和Python2的排序方法不能通用,下面的例子只适用于Python3,Python2的排序方法未包含在本文中。
情况一
列表中的元素已经是可比较元素,直接将列表传入sorted函数即可返回一个已排序列表。默认为升序排列,降序排列可以指定reverse参数,例如:
>>>l=[3,5,4,1,8] >>>sorted(l) [1,3,4,5,8] >>>sorted(l,reverse=True) [8,5,4,3,1] >>>
情况二
需要排序的元素是一个元组或者字典,希望根据我指定的关键字来排序,例如有如下两个列表:
l_v1=[('b',2),('a',1),('c',3),('d',4)] l_v2=[ {'fname':'Brian','lname':'Jones','uid':1003}, {'fname':'David','lname':'Beazley','uid':1002}, {'fname':'John','lname':'Cleese','uid':1001}, {'fname':'Big','lname':'Jones','uid':1004} ]
l_v1是一个元组列表,l_v2是一个字典列表。对l_v1我们希望根据元组中第二个元素来排序,对l_v2我们希望根据字典的关键字uid进行排序。
sorted函数接收一个关键字参数key,该参数指定一个可调用函数,函数返回一个值(只要是可比较的),那么sorted函数将根据返回的关键字对列表中的元素进行排序。
例如对上面的例子:
>>>l_v1=[('b',2),('a',1),('c',3),('d',4)] >>>sorted(l_v1,key=lambdax:x[1]) [('a',1),('b',2),('c',3),('d',4)] >>>l_v2=[ {'fname':'Brian','lname':'Jones','uid':1003}, {'fname':'David','lname':'Beazley','uid':1002}, {'fname':'John','lname':'Cleese','uid':1001}, {'fname':'Big','lname':'Jones','uid':1004} ] >>>sorted(l_v2,key=lambdax:x['uid']) [{'lname':'Cleese','uid':1001,'fname':'John'},{'lname':'Beazley','uid':1002,'fname':'David'},{'lname':'Jones','uid':1003,'fname':'Brian'},{'lname':'Jones','uid':1004,'fname':'Big'}]
这里lambda函数是一个常用的技巧。lambda关键字后边的x是该函数接收的参数,冒号后边的表达式是该函数的返回值。对l_v1来说,传递给参数x的就是每一个元组,其返回元组的第二个元素用于排序;对l_v2来说,传递给参数x的就是列表中的每一个字典元素,其返回字典中uid对应的值用于排序。
除了使用匿名函数lambda这种通用的方法外,Python标准库operator为我们提供了一个itemgetter函数替代我们写的lambda函数,且其性能会比使用lambda函数略有提升。
>>>fromoperatorimportitemgetter >>>l_v1=[('b',2),('a',1),('c',3),('d',4)] >>>sorted(l_v1,key=itemgetter(1)) [('a',1),('b',2),('c',3),('d',4)] >>>l_v2=[ {'fname':'Brian','lname':'Jones','uid':1003}, {'fname':'David','lname':'Beazley','uid':1002}, {'fname':'John','lname':'Cleese','uid':1001}, {'fname':'Big','lname':'Jones','uid':1004} ] >>>sorted(l_v2,key=itemgetter('uid')) [ {'lname':'Cleese','uid':1001,'fname':'John'}, {'lname':'Beazley','uid':1002,'fname':'David'}, {'lname':'Jones','uid':1003,'fname':'Brian'}, {'lname':'Jones','uid':1004,'fname':'Big'} ]
以上例子均是返回一个单一的值用于排序关键字,前面说过,关键字key接收的函数可以返回任意的可比较对象。例如在python中,元组是可以比较的。对元组的比较规则为首先比较元组中第一个位置上的元素,如果相等,在比较第二个位置上的元素,依次类推。回到l_v2的例子,假设现在需求变了,我们首先对lname对应的值排序,如果lname对应的值相等,那么再根据fname确定其顺序。
>>>l_v2=[ {'fname':'Brian','lname':'Jones','uid':1003}, {'fname':'David','lname':'Beazley','uid':1002}, {'fname':'John','lname':'Cleese','uid':1001}, {'fname':'Big','lname':'Jones','uid':1004} ] >>>sorted(l_v2,key=lambdax:(x['lname'],x['fname'])) [ {'lname':'Beazley','uid':1002,'fname':'David'}, {'lname':'Cleese','uid':1001,'fname':'John'}, {'lname':'Jones','uid':1004,'fname':'Big'}, {'lname':'Jones','uid':1003,'fname':'Brian'} ]
这个例子中,lambda函数返回的不再是一个标量值,而是一个元组(x['lname'],x['fname']),根据元组的比较规则,首先根据元组的第一个位置上的元素x['lname']的大小排序,由于列表中有两个字典其lname对应的值都为Jones,因此再根据元组第二个位置的元素x['fname']的值排序,由于Big比Brian要小(按字母顺序依次比较),所以Big排在了前面。
同样使用itemgetter函数也是可以的,且性能会略有提升。此外我觉得itemgetter比lambda更加简洁和可读一点。
>>>l_v2=[ {'fname':'Brian','lname':'Jones','uid':1003}, {'fname':'David','lname':'Beazley','uid':1002}, {'fname':'John','lname':'Cleese','uid':1001}, {'fname':'Big','lname':'Jones','uid':1004} ] >>>sorted(l_v2,key=itemgetter('lname','fname')) [ {'lname':'Beazley','uid':1002,'fname':'David'}, {'lname':'Cleese','uid':1001,'fname':'John'}, {'lname':'Jones','uid':1004,'fname':'Big'}, {'lname':'Jones','uid':1003,'fname':'Brian'} ]
情况三
需要排序的元素是一个Python对象,我们希望根据其某个属性值来排序。例如一个存放User对象的列表如下,根据其name属性排序:
classUser: def__init__(self,name): self.name=name def__str__(self): return'User:%s'%self.name __repr__=__str__#为了能够让User在解释器中显示为'User:name'的格式 user_list=[User('John'),User('David'),User('Big'),User('Alen')]
方法与前面的一样,定义一个函数返回User的name属性的值,把该函数传给sorted的key参数。
>>>user_list=[User('John'),User('David'),User('Big'),User('Alen')] >>>sorted(user_list,key=lambdax:x.name) >>>sorted(user_list,key=lambdax:x.name) [User:Alen,User:Big,User:David,User:John]
但是,itemgetter方法不再起作用,取而代之的是attrgetter方法。
>>>sorted(user_list,key=attrgetter('name')) [User:Alen,User:Big,User:David,User:John]
attrgetter与itemgetter用法完全一致,只是itemgetter用于获取某个位置索引或者字典关键字的取值,而attrgetter用于获取对象的属性值。
PS:sorted返回的是原始列表的一个已排序的副本,而原始列表的顺序并没有任何变化。如果你只想就地排序(即排序原始列表本身),则直接调用list的sort方法即可:list.sort()。其用法与sorted函数一样,只是该函数没有返回值,调用后原始列表已变为一个已排序列表。
对序列中的元素进行分组
和排序类似,现想根据列表中元素的某个关键字分组,使关键字相同的元素分到同一组,并可以对分好的组进行进一步处理。例如有如下的一个列表:
rows=[ {'address':'5412NCLARK','date':'07/01/2012'}, {'address':'5148NCLARK','date':'07/04/2012'}, {'address':'5800E58TH','date':'07/02/2012'}, {'address':'2122NCLARK','date':'07/03/2012'}, {'address':'5645NRAVENSWOOD','date':'07/02/2012'}, {'address':'1060WADDISON','date':'07/02/2012'}, {'address':'4801NBROADWAY','date':'07/01/2012'}, {'address':'1039WGRANVILLE','date':'07/04/2012'}, ]
列表的元素为字典,现想根据字典的date分组,使日期(date)相同的元素分到一个组。Python的itertools模块中的groupby函数可以很好地解决该问题。为了使用groupby函数,首先需要对列表排序:
>>>fromoperatorimportitemgetter >>>sorted_rows=sorted(rows,key=itemgetter('date'))
groupby也和sorted一样有一个key关键字参数,其接收一个可调用函数,该函数返回的值被用做分组的关键字,其用法和sorted的key关键字参数一样。
>>>fordate,itemsingroupby(sorted_rows,key=itemgetter('date')): print(date) foriinitems: print('',i) 07/01/2012 {'address':'5412NCLARK','date':'07/01/2012'} {'address':'4801NBROADWAY','date':'07/01/2012'} 07/02/2012 {'address':'5800E58TH','date':'07/02/2012'} {'address':'5645NRAVENSWOOD','date':'07/02/2012'} {'address':'1060WADDISON','date':'07/02/2012'} 07/03/2012 {'address':'2122NCLARK','date':'07/03/2012'} 07/04/2012 {'address':'5148NCLARK','date':'07/04/2012'} {'address':'1039WGRANVILLE','date':'07/04/2012'}
可以看到groupby返回的值分别是用于分组的关键字对应的值和该组的全部成员。groupby实际返回一个生成器,通过迭代即可分别对各组进行处理。值得注意的一点是,分组前对列表排序这一步必不可少,否则对于非紧邻的元素即使其值相同也会被分在不同组。
总结
以上就是关于python序列进阶篇的全部内容,希望本文的内容对大家学习或者使用python能有所帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。