Python 序列操作進(jìn)階

Python 的序列(sequence)通常指一個(gè)可迭代的容器则北,容器中可以存放任意類型的元素。其中列表(list)是使用的最多的容器痕慢。本文講解了列表推導(dǎo)式尚揣、切片命名、列表元素排序掖举、列表元素分組的使用方法快骗。學(xué)習(xí)了 Python 基本的列表操作后,學(xué)習(xí)這些進(jìn)階的操作,讓我們寫出的代碼更加優(yōu)雅簡(jiǎn)潔和 pythonic 方篮。

列表推導(dǎo)式

當(dāng)我們想要根據(jù)某些規(guī)則來構(gòu)造一個(gè)列表時(shí)名秀,首先想到的應(yīng)該是列表推導(dǎo)式。列表推導(dǎo)式簡(jiǎn)化了循環(huán)操作藕溅,例如我們想要從一個(gè)原始文件名列表中獲取全部 .py 文件匕得,在沒有列表推導(dǎo)式的情況下,我們通常會(huì)這樣做:

file_list = ['foo.py', 'bar.txt', 'spam.py', 'animal.png', 'test.py']
py_list = []

for file in file_list:
    if file.endswith('.py'):
        py_list.append(file)
        
print(py_list)
# output
['foo.py', 'spam.py', 'test.py']

而如果使用列表推導(dǎo)式則可簡(jiǎn)化為:

py_list = [f for f in file_list if f.endswith('.py')]
print(py_list)

# output
['foo.py', 'spam.py', 'test.py']

列表推導(dǎo)式的介紹網(wǎng)上資源很多巾表,不再贅述汁掠。這里只強(qiáng)調(diào),當(dāng)你需要根據(jù)某個(gè)規(guī)則來構(gòu)造一個(gè)列表時(shí)集币,首先應(yīng)該想一想考阱,能否使用簡(jiǎn)潔的列表推導(dǎo)式來實(shí)現(xiàn)該需求,否則再回到常規(guī)的方式鞠苟。

為切片命名

Python 的列表切片使用起來非常方便乞榨,但有時(shí)也會(huì)影響代碼可讀性。例如有一個(gè)字符串:

record = '..........19.6..........100..........'

19.6 為產(chǎn)品價(jià)格当娱,100 為產(chǎn)品數(shù)量姜凄,那么計(jì)算總價(jià)格為:

total_price = float(record[10:14])*int(record[24:27])

但是如果這樣寫,可能過一段時(shí)間我們?cè)賮碜x代碼時(shí)已經(jīng)忘記了 record[10:14] 趾访、record[24:27] 切出來的究竟是什么?為了解決上述問題董虱,可以給切片命個(gè)名來增強(qiáng)可讀性扼鞋。

record = '..........19.6..........100..........'
price = slice(10, 14)
count = slice(24, 27)
total_price = float(record[price])*int(record[count])

slice 接收的參數(shù)格式為 slice(stop)slice(start, stop[, step])愤诱。如果只接收了一個(gè)參數(shù)云头,則等價(jià)于切片語(yǔ)法 [:stop] ,如果接收兩個(gè)參數(shù)淫半,則等價(jià)于切片語(yǔ)法 [start:stop] 溃槐,如果接收三個(gè)參數(shù),則等價(jià)于切片語(yǔ)法 [start:stop:step] 科吭。

排序

排序相關(guān)的任務(wù)通常由內(nèi)置函數(shù) sorted 完成昏滴。需要排序的元素一般存放在一個(gè)列表容器中,列表可以存放任意類型的元素对人,而 sorted 函數(shù)的 key 關(guān)鍵字使得我們能夠輕松地指定元素排序的關(guān)鍵字谣殊,讓排序變得異常簡(jiǎn)單。下面將給出幾個(gè)常見的排序例子以說明 key 關(guān)鍵字的使用方法牺弄。注意 Python3Python2 的排序方法不能通用姻几,下面的例子只適用于 Python3Python2 的排序方法未包含在本文中。

情況一

列表中的元素已經(jīng)是可比較元素蛇捌,直接將列表傳入 sorted 函數(shù)即可返回一個(gè)已排序列表抚恒。默認(rèn)為升序排列,降序排列可以指定 reverse 參數(shù)络拌,例如:

>>> l = [3,5,4,1,8]
>>> sorted(l)
[1, 3, 4, 5, 8]

>>> sorted(l, reverse=True)
[8, 5, 4, 3, 1]
>>> 

情況二

需要排序的元素是一個(gè)元組或者字典俭驮,希望根據(jù)我指定的關(guān)鍵字來排序,例如有如下兩個(gè)列表:

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 是一個(gè)元組列表盒音, l_v2 是一個(gè)字典列表表鳍。對(duì) l_v1 我們希望根據(jù)元組中第二個(gè)元素來排序,對(duì) l_v2 我們希望根據(jù)字典的關(guān)鍵字 uid 進(jìn)行排序祥诽。

sorted 函數(shù)接收一個(gè)關(guān)鍵字參數(shù) key 譬圣,該參數(shù)指定一個(gè)可調(diào)用函數(shù),函數(shù)返回一個(gè)值(只要是可比較的)雄坪,那么 sorted 函數(shù)將根據(jù)返回的關(guān)鍵字對(duì)列表中的元素進(jìn)行排序厘熟。例如對(duì)上面的例子:

>>> l_v1 = [('b',2),('a',1),('c',3),('d',4)]
>>> sorted(l_v1, key=lambda x: 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=lambda x: 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 函數(shù)是一個(gè)常用的技巧。lambda 關(guān)鍵字后邊的 x 是該函數(shù)接收的參數(shù)维哈,冒號(hào)后邊的表達(dá)式是該函數(shù)的返回值绳姨。對(duì) l_v1 來說,傳遞給參數(shù) x 的就是每一個(gè)元組阔挠,其返回元組的第二個(gè)元素用于排序飘庄;對(duì) l_v2 來說,傳遞給參數(shù) x 的就是列表中的每一個(gè)字典元素购撼,其返回字典中 uid 對(duì)應(yīng)的值用于排序跪削。

除了使用匿名函數(shù) lambda 這種通用的方法外,Python 標(biāo)準(zhǔn)庫(kù) operator 為我們提供了一個(gè) itemgetter 函數(shù)替代我們寫的 lambda 函數(shù)迂求,且其性能會(huì)比使用 lambda 函數(shù)略有提升碾盐。

>>> from operator import itemgetter
>>> 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'}
]

以上例子均是返回一個(gè)單一的值用于排序關(guān)鍵字,前面說過揩局,關(guān)鍵字 key 接收的函數(shù)可以返回任意的可比較對(duì)象毫玖。例如在 python 中,元組是可以比較的凌盯。對(duì)元組的比較規(guī)則為首先比較元組中第一個(gè)位置上的元素付枫,如果相等,在比較第二個(gè)位置上的元素十气,依次類推励背。回到 l_v2 的例子砸西,假設(shè)現(xiàn)在需求變了叶眉,我們首先對(duì) lname 對(duì)應(yīng)的值排序址儒,如果 lname 對(duì)應(yīng)的值相等,那么再根據(jù) 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=lambda x: (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'}
]

這個(gè)例子中莲趣,lambda 函數(shù)返回的不再是一個(gè)標(biāo)量值,而是一個(gè)元組 (x['lname'], x['fname']) 饱溢,根據(jù)元組的比較規(guī)則喧伞,首先根據(jù)元組的第一個(gè)位置上的元素 x['lname'] 的大小排序,由于列表中有兩個(gè)字典其 lname 對(duì)應(yīng)的值都為 Jones绩郎,因此再根據(jù)元組第二個(gè)位置的元素 x['fname'] 的值排序潘鲫,由于 BigBrian 要小(按字母順序依次比較)肋杖,所以 Big 排在了前面溉仑。

同樣使用 itemgetter 函數(shù)也是可以的,且性能會(huì)略有提升状植。此外我覺得 itemgetterlambda 更加簡(jiǎn)潔和可讀一點(diǎn)浊竟。

>>> 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'}
]

情況三

需要排序的元素是一個(gè) Python 對(duì)象,我們希望根據(jù)其某個(gè)屬性值來排序津畸。例如一個(gè)存放 User 對(duì)象的列表如下振定,根據(jù)其 name 屬性排序:

class User:
    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')]

方法與前面的一樣,定義一個(gè)函數(shù)返回 Username 屬性的值肉拓,把該函數(shù)傳給 sortedkey 參數(shù)后频。

>>> user_list = [User('John'), User('David'), User('Big'), User('Alen')]
>>> sorted(user_list, key=lambda x: x.name)
>>> sorted(user_list, key=lambda x: 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]

attrgetteritemgetter 用法完全一致,只是 itemgetter 用于獲取某個(gè)位置索引或者字典關(guān)鍵字的取值丧肴,而 attrgetter 用于獲取對(duì)象的屬性值。

PS:sorted 返回的是原始列表的一個(gè)已排序的副本胧后,而原始列表的順序并沒有任何變化芋浮。如果你只想就地排序(即排序原始列表本身),則直接調(diào)用 listsort 方法即可:list.sort()壳快。其用法與 sorted 函數(shù)一樣纸巷,只是該函數(shù)沒有返回值,調(diào)用后原始列表已變?yōu)橐粋€(gè)已排序列表眶痰。

對(duì)序列中的元素進(jìn)行分組

和排序類似瘤旨,現(xiàn)想根據(jù)列表中元素的某個(gè)關(guān)鍵字分組,使關(guān)鍵字相同的元素分到同一組竖伯,并可以對(duì)分好的組進(jìn)行進(jìn)一步處理存哲。例如有如下的一個(gè)列表:

rows = [
    {'address': '5412 N CLARK', 'date': '07/01/2012'},
    {'address': '5148 N CLARK', 'date': '07/04/2012'},
    {'address': '5800 E 58TH', 'date': '07/02/2012'},
    {'address': '2122 N CLARK', 'date': '07/03/2012'},
    {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
    {'address': '1060 W ADDISON', 'date': '07/02/2012'},
    {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
    {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]

列表的元素為字典因宇,現(xiàn)想根據(jù)字典的 date 分組,使日期( date )相同的元素分到一個(gè)組祟偷。Pythonitertools 模塊中的 groupby 函數(shù)可以很好地解決該問題察滑。為了使用 groupby 函數(shù),首先需要對(duì)列表排序:

>>> from operator import itemgetter
>>> sorted_rows = sorted(rows, key=itemgetter('date'))

groupby 也和 sorted 一樣有一個(gè) key 關(guān)鍵字參數(shù)修肠,其接收一個(gè)可調(diào)用函數(shù)贺辰,該函數(shù)返回的值被用做分組的關(guān)鍵字,其用法和 sortedkey 關(guān)鍵字參數(shù)一樣 嵌施。

>>> for date, items in groupby(sorted_rows, key=itemgetter('date')):
    print(date)
    for i in items:
        print(' ', i)
    
07/01/2012
  {'address': '5412 N CLARK', 'date': '07/01/2012'}
  {'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
  {'address': '5800 E 58TH', 'date': '07/02/2012'}
  {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
  {'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
  {'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
  {'address': '5148 N CLARK', 'date': '07/04/2012'}
  {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}

可以看到 groupby 返回的值分別是用于分組的關(guān)鍵字對(duì)應(yīng)的值和該組的全部成員饲化。groupby 實(shí)際返回一個(gè)生成器,通過迭代即可分別對(duì)各組進(jìn)行處理吗伤。值得注意的一點(diǎn)是吃靠,分組前對(duì)列表排序這一步必不可少,否則對(duì)于非緊鄰的元素即使其值相同也會(huì)被分在不同組牲芋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撩笆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缸浦,更是在濱河造成了極大的恐慌夕冲,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裂逐,死亡現(xiàn)場(chǎng)離奇詭異歹鱼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卜高,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門纺荧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榔至,“玉大人,你說我怎么就攤上這事⊥眨” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵漾唉,是天一觀的道長(zhǎng)炬称。 經(jīng)常有香客問我,道長(zhǎng)拣帽,這世上最難降的妖魔是什么疼电? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮减拭,結(jié)果婚禮上蔽豺,老公的妹妹穿的比我還像新娘。我一直安慰自己拧粪,他們只是感情好修陡,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布沧侥。 她就那樣靜靜地躺著,像睡著了一般濒析。 火紅的嫁衣襯著肌膚如雪正什。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天号杏,我揣著相機(jī)與錄音婴氮,去河邊找鬼。 笑死盾致,一個(gè)胖子當(dāng)著我的面吹牛主经,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庭惜,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼罩驻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了护赊?” 一聲冷哼從身側(cè)響起惠遏,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骏啰,沒想到半個(gè)月后节吮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡判耕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年透绩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壁熄。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帚豪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出草丧,到底是詐尸還是另有隱情狸臣,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布昌执,位于F島的核電站固棚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仙蚜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一厂汗、第九天 我趴在偏房一處隱蔽的房頂上張望委粉。 院中可真熱鬧,春花似錦娶桦、人聲如沸贾节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栗涂。三九已至知牌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斤程,已是汗流浹背角寸。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忿墅,地道東北人扁藕。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疚脐,于是被迫代替她去往敵國(guó)和親亿柑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容