2.3 序列
來(lái)源:2.3 Sequences
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
序列是數(shù)據(jù)值的順序容器亏狰。不像偶對(duì)只有兩個(gè)元素,序列可以擁有任意(但是有限)個(gè)有序元素偶摔。
序列在計(jì)算機(jī)科學(xué)中是強(qiáng)大而基本的抽象暇唾。例如,如果我們使用序列辰斋,我們就可以列出伯克利的每個(gè)學(xué)生策州,或者世界上的每所大學(xué),或者每所大學(xué)中的每個(gè)學(xué)生亡呵。我們可以列出上過(guò)的每一門(mén)課抽活,提交的每個(gè)作業(yè),或者得到的每個(gè)成績(jī)锰什。序列抽象讓數(shù)千個(gè)數(shù)據(jù)驅(qū)動(dòng)的程序影響著我們每天的生活下硕。
序列不是特定的抽象數(shù)據(jù)類(lèi)型,而是不同類(lèi)型共有的一組行為汁胆。也就是說(shuō)梭姓,它們是許多序列種類(lèi),但是都有一定的屬性嫩码。特別地誉尖,
長(zhǎng)度。序列擁有有限的長(zhǎng)度铸题。
元素選擇铡恕。序列的每個(gè)元素都擁有相應(yīng)的非負(fù)整數(shù)作為下標(biāo),它小于序列長(zhǎng)度丢间,以第一個(gè)元素的 0 開(kāi)始探熔。
不像抽象數(shù)據(jù)類(lèi)型,我們并沒(méi)有闡述如何構(gòu)造序列烘挫。序列抽象是一組行為诀艰,它們并沒(méi)有完全指定類(lèi)型(例如,使用構(gòu)造器和選擇器)饮六,但是可以在多種類(lèi)型中共享其垄。序列提供了一個(gè)抽象層級(jí),將特定程序如何操作序列類(lèi)型的細(xì)節(jié)隱藏卤橄。
這一節(jié)中绿满,我們開(kāi)發(fā)了一個(gè)特定的抽象數(shù)據(jù)類(lèi)型,它可以實(shí)現(xiàn)序列抽象窟扑。我們之后介紹實(shí)現(xiàn)相同抽象的 Python 內(nèi)建類(lèi)型棒口。
2.3.1 嵌套偶對(duì)
對(duì)于有理數(shù)寄月,我們使用二元組將兩個(gè)整數(shù)對(duì)象配對(duì),之后展示了我們可以同樣通過(guò)函數(shù)來(lái)實(shí)現(xiàn)偶對(duì)无牵。這種情況下,每個(gè)我們構(gòu)造的偶對(duì)的元素都是整數(shù)厂抖。然而茎毁,就像表達(dá)式,元組可以嵌套忱辅。每個(gè)偶對(duì)的元素本身也可以是偶對(duì)七蜘,這個(gè)特性在實(shí)現(xiàn)偶對(duì)的任意一個(gè)方法,元組或調(diào)度函數(shù)中都有效墙懂。
可視化偶對(duì)的一個(gè)標(biāo)準(zhǔn)方法 -- 這里也就是偶對(duì)(1,2)
-- 叫做盒子和指針記號(hào)橡卤。每個(gè)值,復(fù)合或原始损搬,都描述為指向盒子的指針碧库。原始值的盒子只包含那個(gè)值的表示。例如巧勤,數(shù)值的盒子只包含數(shù)字嵌灰。偶對(duì)的盒子實(shí)際上是兩個(gè)盒子:左邊的部分(箭頭指向的)包含偶對(duì)的第一個(gè)元素,右邊的部分包含第二個(gè)颅悉。

嵌套元素的 Python 表達(dá)式:
>>> ((1, 2), (3, 4))
((1, 2), (3, 4))
具有下面的結(jié)構(gòu):

使用元組作為其它元組元素的能力沽瞭,提供了我們編程語(yǔ)言中的一個(gè)新的組合手段。我們將這種將元組以這種方式嵌套的能力叫做元組數(shù)據(jù)類(lèi)型的封閉性剩瓶。通常驹溃,如果組合結(jié)果自己可以使用相同的方式組合,組合數(shù)據(jù)值的方式就滿(mǎn)足封閉性延曙。封閉性在任何組合手段中都是核心能力豌鹤,因?yàn)樗试S我們創(chuàng)建層次數(shù)據(jù)結(jié)構(gòu) -- 結(jié)構(gòu)由多個(gè)部分組成,它們自己也由多個(gè)部分組成搂鲫,以此類(lèi)推傍药。我們?cè)诘谌聲?huì)探索一些層次結(jié)構(gòu)。現(xiàn)在魂仍,我們考慮一個(gè)特定的重要結(jié)構(gòu)拐辽。
2.3.2 遞歸列表
我們可以使用嵌套偶對(duì)來(lái)構(gòu)建任意長(zhǎng)度的元素列表,它讓我們能夠?qū)崿F(xiàn)抽象序列擦酌。下面的圖展示了四元素列表1, 2, 3, 4
的遞歸表示:

這個(gè)列表由一系列偶對(duì)表示俱诸。每個(gè)偶對(duì)的第一個(gè)元素是列表中的元素,而第二個(gè)元素是用于表示列表其余部分的偶對(duì)赊舶。最后一個(gè)偶對(duì)的第二個(gè)元素是None
睁搭,它表明列表到末尾了赶诊。我們可以使用嵌套的元組字面值來(lái)構(gòu)造這個(gè)結(jié)構(gòu):
>>> (1, (2, (3, (4, None))))
(1, (2, (3, (4, None))))
這個(gè)嵌套的結(jié)構(gòu)通常對(duì)應(yīng)了一種非常實(shí)用的序列思考方式,我們?cè)?Python 解釋器的執(zhí)行規(guī)則中已經(jīng)見(jiàn)過(guò)它了园骆。一個(gè)非空序列可以劃分為:
- 它的第一個(gè)元素舔痪,以及
- 序列的其余部分。
序列的其余部分本身就是一個(gè)(可能為空的)序列锌唾。我們將序列的這種看法叫做遞歸锄码,因?yàn)樾蛄邪渌蛄凶鳛榈诙€(gè)組成部分。
由于我們的列表表示是遞歸的晌涕,我們?cè)趯?shí)現(xiàn)中叫它rlist
滋捶,以便不會(huì)和 Python 內(nèi)建的list
類(lèi)型混淆,我們會(huì)稍后在這一章介紹它余黎。一個(gè)遞歸列表可以由第一個(gè)元素和列表的剩余部分構(gòu)造重窟。None
值表示空的遞歸列表。
>>> empty_rlist = None
>>> def make_rlist(first, rest):
"""Make a recursive list from its first element and the rest."""
return (first, rest)
>>> def first(s):
"""Return the first element of a recursive list s."""
return s[0]
>>> def rest(s):
"""Return the rest of the elements of a recursive list s."""
return s[1]
這兩個(gè)選擇器和一個(gè)構(gòu)造器惧财,以及一個(gè)常量共同實(shí)現(xiàn)了抽象數(shù)據(jù)類(lèi)型的遞歸列表巡扇。遞歸列表唯一的行為條件是,就像偶對(duì)那樣可缚,它的構(gòu)造器和選擇器是相反的函數(shù)霎迫。
- 如果一個(gè)遞歸列表
s
由元素f
和列表r
構(gòu)造,那么first(s)
返回f
帘靡,并且rest(s)
返回r
知给。
我們可以使用構(gòu)造器和選擇器來(lái)操作遞歸列表。
>>> counts = make_rlist(1, make_rlist(2, make_rlist(3, make_rlist(4, empty_rlist))))
>>> first(counts)
1
>>> rest(counts)
(2, (3, (4, None)))
遞歸列表可以按序儲(chǔ)存元素序列描姚,但是它還沒(méi)有實(shí)現(xiàn)序列的抽象涩赢。使用我們已經(jīng)定義的數(shù)據(jù)類(lèi)型抽象,我們就可以實(shí)現(xiàn)描述兩個(gè)序列的行為:長(zhǎng)度和元素選擇轩勘。
>>> def len_rlist(s):
"""Return the length of recursive list s."""
length = 0
while s != empty_rlist:
s, length = rest(s), length + 1
return length
>>> def getitem_rlist(s, i):
"""Return the element at index i of recursive list s."""
while i > 0:
s, i = rest(s), i - 1
return first(s)
現(xiàn)在筒扒,我們可以將遞歸列表用作序列了:
>>> len_rlist(counts)
4
>>> getitem_rlist(counts, 1) # The second item has index 1
2
兩個(gè)實(shí)現(xiàn)都是可迭代的。它們隔離了嵌套偶對(duì)的每個(gè)層級(jí)绊寻,直到列表的末尾(在len_rlist
中)花墩,或者到達(dá)了想要的元素(在getitem_rlist
中)。
下面的一系列環(huán)境圖示展示了迭代過(guò)程澄步,getitem_rlist
通過(guò)它找到了遞歸列表中下標(biāo)1
中的元素2
冰蘑。

while
頭部中的表達(dá)式求值為真,這會(huì)導(dǎo)致while
語(yǔ)句組中的賦值語(yǔ)句被執(zhí)行:

這里村缸,局部名稱(chēng)s
現(xiàn)在指向以原列表第二個(gè)元素開(kāi)始的子列表§舴剩現(xiàn)在,while
頭中的表達(dá)式求值為假梯皿,于是 Python 會(huì)求出getitem_rlist
最后一行中返回語(yǔ)句中的表達(dá)式仇箱。

最后的環(huán)境圖示展示了調(diào)用first
的局部幀县恕,它包含綁定到相同子列表的s
。first
函數(shù)挑選出值2
并返回了它剂桥,完成了getitem_rlist
的調(diào)用忠烛。
這個(gè)例子演示了遞歸列表計(jì)算的常見(jiàn)模式,其中迭代的每一步都操作原列表的一個(gè)逐漸變短的后綴渊额。尋找遞歸列表的長(zhǎng)度和元素的漸進(jìn)式處理過(guò)程需要一些時(shí)間來(lái)計(jì)算况木。(第三章中,我們會(huì)學(xué)會(huì)描述這種函數(shù)的計(jì)算時(shí)間旬迹。)Python 的內(nèi)建序列類(lèi)型以不同方式實(shí)現(xiàn),它對(duì)于計(jì)算序列長(zhǎng)度和獲取元素并不具有大量的計(jì)算開(kāi)銷(xiāo)求类。
2.3.2 元組 II
實(shí)際上奔垦,我們引入用于形成原始偶對(duì)的tuple
類(lèi)型本身就是完整的序列類(lèi)型。元組比起我們以函數(shù)式實(shí)現(xiàn)的偶對(duì)抽象數(shù)據(jù)結(jié)構(gòu)尸疆,本質(zhì)上提供了更多功能椿猎。
元組具有任意的長(zhǎng)度,并且也擁有序列抽象的兩個(gè)基本行為:長(zhǎng)度和元素選擇寿弱。下面的digits
是一個(gè)四元素元組犯眠。
>>> digits = (1, 8, 2, 8)
>>> len(digits)
4
>>> digits[3]
8
此外,元素可以彼此相加以及與整數(shù)相乘症革。對(duì)于元組筐咧,加法和乘法操作并不對(duì)元素相加或相乘,而是組合和重復(fù)元組本身噪矛。也就是說(shuō)量蕊,operator
模塊中的add
函數(shù)(以及+
運(yùn)算符)返回兩個(gè)被加參數(shù)連接成的新元組。operator
模塊中的mul
函數(shù)(以及*
運(yùn)算符)接受整數(shù)k
和元組艇挨,并返回含有元組參數(shù)k
個(gè)副本的新元組残炮。
>>> (2, 7) + digits * 2
(2, 7, 1, 8, 2, 8, 1, 8, 2, 8)
映射。將一個(gè)元組變換為另一個(gè)元組的強(qiáng)大手段是在每個(gè)元素上調(diào)用函數(shù)缩滨,并收集結(jié)果势就。這一計(jì)算的常用形式叫做在序列上映射函數(shù),對(duì)應(yīng)內(nèi)建函數(shù)map
脉漏。map
的結(jié)果是一個(gè)本身不是序列的對(duì)象苞冯,但是可以通過(guò)調(diào)用tuple
來(lái)轉(zhuǎn)換為序列。它是元組的構(gòu)造器鸠删。
>>> alternates = (-1, 2, -3, 4, -5)
>>> tuple(map(abs, alternates))
(1, 2, 3, 4, 5)
map
函數(shù)非常重要抱完,因?yàn)樗蕾?lài)于序列抽象:我們不需要關(guān)心底層元組的結(jié)構(gòu),只需要能夠獨(dú)立訪(fǎng)問(wèn)每個(gè)元素刃泡,以便將它作為參數(shù)傳入用于映射的函數(shù)中(這里是abs
)巧娱。
2.3.4 序列迭代
映射本身就是通用計(jì)算模式的一個(gè)實(shí)例:在序列中迭代所有元素碉怔。為了在序列上映射函數(shù),我們不僅僅需要選擇特定的元素禁添,還要依次選擇每個(gè)元素撮胧。這個(gè)模式非常普遍,Python 擁有額外的控制語(yǔ)句來(lái)處理序列數(shù)據(jù):for
語(yǔ)句老翘。
考慮一個(gè)問(wèn)題芹啥,計(jì)算一個(gè)值在序列中出現(xiàn)了多少次。我們可以使用while
循環(huán)實(shí)現(xiàn)一個(gè)函數(shù)來(lái)計(jì)算這個(gè)數(shù)量铺峭。
>>> def count(s, value):
"""Count the number of occurrences of value in sequence s."""
total, index = 0, 0
while index < len(s):
if s[index] == value:
total = total + 1
index = index + 1
return total
>>> count(digits, 8)
2
Python for
語(yǔ)句可以通過(guò)直接迭代元素值來(lái)簡(jiǎn)化這個(gè)函數(shù)體墓怀,完全不需要引入index
。例如(原文是For example
卫键,為雙關(guān)語(yǔ))傀履,我們可以寫(xiě)成:
>>> def count(s, value):
"""Count the number of occurrences of value in sequence s."""
total = 0
for elem in s:
if elem == value:
total = total + 1
return total
>>> count(digits, 8)
2
for
語(yǔ)句按照以下過(guò)程來(lái)執(zhí)行:
- 求出頭部表達(dá)式
<expression>
,它必須產(chǎn)生一個(gè)可迭代的值莉炉。 - 對(duì)于序列中的每個(gè)元素值钓账,按順序:
- 在局部環(huán)境中將變量名
<name>
綁定到這個(gè)值上。 - 執(zhí)行語(yǔ)句組
<suite>
絮宁。
- 在局部環(huán)境中將變量名
步驟 1 引用了可迭代的值梆暮。序列是可迭代的,它們的元素可看做迭代的順序绍昂。Python 的確擁有其他可迭代類(lèi)型啦粹,但是我們現(xiàn)在只關(guān)注序列。術(shù)語(yǔ)“可迭代對(duì)象”的一般定義會(huì)在第四章的迭代器一節(jié)中出現(xiàn)治专。
這個(gè)求值過(guò)程的一個(gè)重要結(jié)果是卖陵,在for
語(yǔ)句執(zhí)行完畢之后,<name>
會(huì)綁定到序列的最后一個(gè)元素上张峰。這個(gè)for
循環(huán)引入了另一種方式泪蔫,其中局部環(huán)境可以由語(yǔ)句來(lái)更新。
序列解構(gòu)喘批。程序中的一個(gè)常見(jiàn)模式是撩荣,序列的元素本身就是序列,但是具有固定的長(zhǎng)度饶深。for
語(yǔ)句可在頭部中包含多個(gè)名稱(chēng)餐曹,將每個(gè)元素序列“解構(gòu)”為各個(gè)元素。例如敌厘,我們擁有一個(gè)偶對(duì)(也就是二元組)的序列:
>>> pairs = ((1, 2), (2, 2), (2, 3), (4, 4))
下面的for
語(yǔ)句的頭部帶有兩個(gè)名詞台猴,會(huì)將每個(gè)名稱(chēng)x
和y
分別綁定到每個(gè)偶對(duì)的第一個(gè)和第二個(gè)元素上。
>>> for x, y in pairs:
if x == y:
same_count = same_count + 1
>>> same_count
2
這個(gè)綁定多個(gè)名稱(chēng)到定長(zhǎng)序列中多個(gè)值的模式,叫做序列解構(gòu)饱狂。它的模式和我們?cè)谫x值語(yǔ)句中看到的曹步,將多個(gè)名稱(chēng)綁定到多個(gè)值的模式相同。
范圍休讳。range
是另一種 Python 的內(nèi)建序列類(lèi)型讲婚,它表示一個(gè)整數(shù)范圍。范圍可以使用range
函數(shù)來(lái)創(chuàng)建俊柔,它接受兩個(gè)整數(shù)參數(shù):所得范圍的第一個(gè)數(shù)值和最后一個(gè)數(shù)值加一筹麸。
>>> range(1, 10) # Includes 1, but not 10
range(1, 10)
在范圍上調(diào)用tuple
構(gòu)造器會(huì)創(chuàng)建與范圍具有相同元素的元組,使元素易于查看雏婶。
>>> tuple(range(5, 8))
(5, 6, 7)
如果只提供了一個(gè)元素物赶,它會(huì)解釋為最后一個(gè)數(shù)值加一,范圍開(kāi)始于 0留晚。
>>> total = 0
>>> for k in range(5, 8):
total = total + k
>>> total
18
常見(jiàn)的慣例是將單下劃線(xiàn)字符用于for
頭部块差,如果這個(gè)名稱(chēng)在語(yǔ)句組中不會(huì)使用。
>>> for _ in range(3):
print('Go Bears!')
Go Bears!
Go Bears!
Go Bears!
要注意對(duì)解釋器來(lái)說(shuō)倔丈,下劃線(xiàn)只是另一個(gè)名稱(chēng),但是在程序員中具有固定含義状蜗,它表明這個(gè)名稱(chēng)不應(yīng)出現(xiàn)在任何表達(dá)式中需五。
2.3.5 序列抽象
我們已經(jīng)介紹了兩種原生數(shù)據(jù)類(lèi)型,它們實(shí)現(xiàn)了序列抽象:元組和范圍轧坎。兩個(gè)都滿(mǎn)足這一章開(kāi)始時(shí)的條件:長(zhǎng)度和元素選擇宏邮。Python 還包含了兩種序列類(lèi)型的行為,它們擴(kuò)展了序列抽象缸血。
成員性蜜氨。可以測(cè)試一個(gè)值在序列中的成員性。Python 擁有兩個(gè)操作符in
和not in
捎泻,取決于元素是否在序列中出現(xiàn)而求值為True
和False
飒炎。
>>> digits
(1, 8, 2, 8)
>>> 2 in digits
True
>>> 1828 not in digits
True
所有序列都有叫做index
和count
的方法,它會(huì)返回序列中某個(gè)值的下標(biāo)(或者數(shù)量)笆豁。
切片郎汪。序列包含其中的子序列。我們?cè)陂_(kāi)發(fā)我們的嵌套偶對(duì)實(shí)現(xiàn)時(shí)觀(guān)察到了這一點(diǎn)闯狱,它將序列切分為它的第一個(gè)元素和其余部分煞赢。序列的切片是原序列的任何部分,由一對(duì)整數(shù)指定哄孤。就像range
構(gòu)造器那樣照筑,第一個(gè)整數(shù)表示切片的起始下標(biāo),第二個(gè)表示結(jié)束下標(biāo)加一。
Python 中凝危,序列切片的表示類(lèi)似于元素選擇波俄,使用方括號(hào)。冒號(hào)分割了起始和結(jié)束下標(biāo)媒抠。任何邊界上的省略都被當(dāng)作極限值:起始下標(biāo)為 0弟断,結(jié)束下標(biāo)是序列長(zhǎng)度。
>>> digits[0:2]
(1, 8)
>>> digits[1:]
(8, 2, 8)
Python 序列抽象的這些額外行為的枚舉趴生,給我們了一個(gè)機(jī)會(huì)來(lái)反思數(shù)據(jù)抽象通常由什么構(gòu)成阀趴。抽象的豐富性(也就是說(shuō)它包含行為的多少)非常重要。對(duì)于使用抽象的用戶(hù)苍匆,額外的行為很有幫助刘急,另一方面,滿(mǎn)足新類(lèi)型抽象的豐富需求是個(gè)挑戰(zhàn)浸踩。為了確保我們的遞歸列表實(shí)現(xiàn)支持這些額外的行為叔汁,需要一些工作量。另一個(gè)抽象豐富性的負(fù)面結(jié)果是检碗,它們需要用戶(hù)長(zhǎng)時(shí)間學(xué)習(xí)据块。
序列擁有豐富的抽象,因?yàn)樗鼈冊(cè)谟?jì)算中無(wú)處不在折剃,所以學(xué)習(xí)一些復(fù)雜的行為是合理的另假。通常,多數(shù)用戶(hù)定義的抽象應(yīng)該盡可能簡(jiǎn)單怕犁。
擴(kuò)展閱讀边篮。切片符號(hào)接受很多特殊情況,例如負(fù)的起始值奏甫,結(jié)束值和步長(zhǎng)戈轿。Dive Into Python 3 中有一節(jié)叫做列表切片,完整描述了它阵子。這一章中思杯,我們只會(huì)用到上面描述的基本特性。
2.3.6 字符串
文本值可能比數(shù)值對(duì)計(jì)算機(jī)科學(xué)來(lái)說(shuō)更基本款筑。作為一個(gè)例子智蝠,Python 程序以文本編寫(xiě)和儲(chǔ)存。Python 中原生的文本數(shù)據(jù)類(lèi)型叫做字符串奈梳,相應(yīng)的構(gòu)造器是str
杈湾。
關(guān)于字符串在 Python 中如何表示和操作有許多細(xì)節(jié)。字符串是豐富抽象的另一個(gè)示例攘须,程序員需要滿(mǎn)足一些實(shí)質(zhì)性要求來(lái)掌握漆撞。這一節(jié)是字符串基本行為的摘要。
字符串字面值可以表達(dá)任意文本,被單引號(hào)或者雙引號(hào)包圍浮驳。
>>> 'I am string!'
'I am string!'
>>> "I've got an apostrophe"
"I've got an apostrophe"
>>> '您好'
'您好'
我們已經(jīng)在代碼中見(jiàn)過(guò)字符串了悍汛,在print
的調(diào)用中作為文檔字符串,以及在assert
語(yǔ)句中作為錯(cuò)誤信息至会。
字符串滿(mǎn)足兩個(gè)基本的序列條件离咐,我們?cè)谶@一節(jié)開(kāi)始介紹過(guò)它們:它們擁有長(zhǎng)度并且支持元素選擇。
>>> city = 'Berkeley'
>>> len(city)
8
>>> city[3]
'k'
字符串的元素本身就是包含單一字符的字符串奉件。字符是字母表中的任意單一字符宵蛀,標(biāo)點(diǎn)符號(hào),或者其它符號(hào)县貌。不像許多其它編程語(yǔ)言那樣术陶,Python 沒(méi)有單獨(dú)的字符類(lèi)型,任何文本都是字符串煤痕,表示單一字符的字符串長(zhǎng)度為 1梧宫、
就像元組,字符串可以通過(guò)加法和乘法來(lái)組合:
>>> city = 'Berkeley'
>>> len(city)
8
>>> city[3]
'k'
字符串的行為不同于 Python 中其它序列類(lèi)型摆碉。字符串抽象沒(méi)有實(shí)現(xiàn)我們?yōu)樵M和范圍描述的完整序列抽象塘匣。特別地,字符串上實(shí)現(xiàn)了成員性運(yùn)算符in
巷帝,但是與序列上的實(shí)現(xiàn)具有完全不同的行為馆铁。它匹配子字符串而不是元素。
>>> 'here' in "Where's Waldo?"
True
與之相似锅睛,字符串上的count
和index
方法接受子串作為參數(shù),而不是單一字符历谍。count
的行為有細(xì)微差別现拒,它統(tǒng)計(jì)字符串中非重疊字串的出現(xiàn)次數(shù)。
>>> 'Mississippi'.count('i')
4
>>> 'Mississippi'.count('issi')
1
多行文本望侈。字符串并不限制于單行文本印蔬,三個(gè)引號(hào)分隔的字符串字面值可以跨越多行。我們已經(jīng)在文檔字符串中使用了三個(gè)引號(hào)脱衙。
>>> """The Zen of Python
claims, Readability counts.
Read more: import this."""
'The Zen of Python\nclaims, "Readability counts."\nRead more: import this.'
在上面的打印結(jié)果中侥猬,\n
(叫做“反斜杠加 n”)是表示新行的單一元素。雖然它表示為兩個(gè)字符(反斜杠和 n)捐韩。它在長(zhǎng)度和元素選擇上被認(rèn)為是單個(gè)字符退唠。
字符串強(qiáng)制。字符串可以從 Python 的任何對(duì)象通過(guò)以某個(gè)對(duì)象值作為參數(shù)調(diào)用str
構(gòu)造函數(shù)來(lái)創(chuàng)建荤胁,這個(gè)字符串的特性對(duì)于從多種類(lèi)型的對(duì)象中構(gòu)造描述性字符串非常實(shí)用瞧预。
>>> str(2) + ' is an element of ' + str(digits)
'2 is an element of (1, 8, 2, 8)'
str
函數(shù)可以以任何類(lèi)型的參數(shù)調(diào)用,并返回合適的值,這個(gè)機(jī)制是后面的泛用函數(shù)的主題垢油。
方法盆驹。字符串在 Python 中的行為非常具有生產(chǎn)力,因?yàn)榇罅康姆椒ǘ挤祷刈址淖凅w或者搜索其內(nèi)容滩愁。一部分這些方法由下面的示例介紹躯喇。
>>> '1234'.isnumeric()
True
>>> 'rOBERT dE nIRO'.swapcase()
'Robert De Niro'
>>> 'snakeyes'.upper().endswith('YES')
True
擴(kuò)展閱讀。計(jì)算機(jī)中的文本編碼是個(gè)復(fù)雜的話(huà)題硝枉。這一章中廉丽,我們會(huì)移走字符串如何表示的細(xì)節(jié),但是檀咙,對(duì)許多應(yīng)用來(lái)說(shuō)雅倒,字符串如何由計(jì)算機(jī)編碼的特定細(xì)節(jié)是必要的知識(shí)。Dive Into Python 3 的 4.1 ~ 4.3 節(jié)提供了字符編碼和 Unicode 的描述弧可。
2.3.7 接口約定
在復(fù)合數(shù)據(jù)的處理中蔑匣,我們強(qiáng)調(diào)了數(shù)據(jù)抽象如何讓我們?cè)O(shè)計(jì)程序而不陷入數(shù)據(jù)表示的細(xì)節(jié),以及抽象如何為我們保留靈活性來(lái)嘗試備用表示棕诵。這一節(jié)中裁良,我們引入了另一種強(qiáng)大的設(shè)計(jì)原則來(lái)處理數(shù)據(jù)結(jié)構(gòu) -- 接口約定的用法。
接口約定使在許多組件模塊中共享的數(shù)據(jù)格式校套,它可以混合和匹配來(lái)展示數(shù)據(jù)价脾。例如,如果我們擁有多個(gè)函數(shù)笛匙,它們?nèi)拷邮苄蛄凶鳛閰?shù)并且返回序列值侨把,我們就可以把它們每一個(gè)用于上一個(gè)的輸出上,并選擇任意一種順序妹孙。這樣秋柄,我們就可以通過(guò)將函數(shù)鏈接成流水線(xiàn),來(lái)創(chuàng)建一個(gè)復(fù)雜的過(guò)程蠢正,每個(gè)函數(shù)都是簡(jiǎn)單而專(zhuān)一的骇笔。
這一節(jié)有兩個(gè)目的,來(lái)介紹以接口約定組織程序的概念嚣崭,以及展示模塊化序列處理的示例笨触。
考慮下面兩個(gè)問(wèn)題,它們首次出現(xiàn)雹舀,并且只和序列的使用相關(guān)芦劣。
- 對(duì)前
n
個(gè)斐波那契數(shù)中的偶數(shù)求和。 - 列出一個(gè)名稱(chēng)中的所有縮寫(xiě)字母说榆,它包含每個(gè)大寫(xiě)單詞的首字母持寄。
這些問(wèn)題是有關(guān)系的源梭,因?yàn)樗鼈兛梢越鈽?gòu)為簡(jiǎn)單的操作,它們接受序列作為輸入稍味,并產(chǎn)出序列作為輸出废麻。而且,這些操作是序列上的計(jì)算的一般方法的實(shí)例模庐。讓我們思考第一個(gè)問(wèn)題烛愧,它可以解構(gòu)為下面的步驟:
enumerate map filter accumulate
----------- --- ------ ----------
naturals(n) fib iseven sum
下面的fib
函數(shù)計(jì)算了斐波那契數(shù)(現(xiàn)在使用了for
語(yǔ)句更新了第一章中的定義)。
>>> def fib(k):
"""Compute the kth Fibonacci number."""
prev, curr = 1, 0 # curr is the first Fibonacci number.
for _ in range(k - 1):
prev, curr = curr, prev + curr
return curr
謂詞iseven
可以使用整數(shù)取余運(yùn)算符%
來(lái)定義掂碱。
>>> def iseven(n):
return n % 2 == 0
map
和filter
函數(shù)是序列操作怜姿,我們已經(jīng)見(jiàn)過(guò)了map
,它在序列中的每個(gè)元素上調(diào)用函數(shù)并且收集結(jié)果疼燥。filter
函數(shù)接受序列沧卢,并且返回序列中謂詞為真的元素。兩個(gè)函數(shù)都返回間接對(duì)象醉者,map
和filter
對(duì)象但狭,它們是可以轉(zhuǎn)換為元組或求和的可迭代對(duì)象。
>>> nums = (5, 6, -7, -8, 9)
>>> tuple(filter(iseven, nums))
(6, -8)
>>> sum(map(abs, nums))
35
現(xiàn)在我們可以實(shí)現(xiàn)even_fib
撬即,第一個(gè)問(wèn)題的解立磁,使用map
、filter
和sum
剥槐。
>>> def sum_even_fibs(n):
"""Sum the first n even Fibonacci numbers."""
return sum(filter(iseven, map(fib, range(1, n+1))))
>>> sum_even_fibs(20)
3382
現(xiàn)在唱歧,讓我們思考第二個(gè)問(wèn)題。它可以解構(gòu)為序列操作的流水線(xiàn)粒竖,包含map
和filter
颅崩。
enumerate filter map accumulate
--------- ------ ----- ----------
words iscap first tuple
字符串中的單詞可以通過(guò)字符串對(duì)象上的split
方法來(lái)枚舉,默認(rèn)以空格分割蕊苗。
>>> tuple('Spaces between words'.split())
('Spaces', 'between', 'words')
單詞的第一個(gè)字母可以使用選擇運(yùn)算符來(lái)獲取挨摸,確定一個(gè)單詞是否大寫(xiě)的謂詞可以使用內(nèi)建謂詞isupper
定義。
>>> def first(s):
return s[0]
>>> def iscap(s):
return len(s) > 0 and s[0].isupper()\
這里岁歉,我們的縮寫(xiě)函數(shù)可以使用map
和filter
定義。
>>> def acronym(name):
"""Return a tuple of the letters that form the acronym for name."""
return tuple(map(first, filter(iscap, name.split())))
>>> acronym('University of California Berkeley Undergraduate Graphics Group')
('U', 'C', 'B', 'U', 'G', 'G')
這些不同問(wèn)題的相似解法展示了如何使用通用的計(jì)算模式膝蜈,例如映射锅移、過(guò)濾和累計(jì),來(lái)組合序列的接口約定上的操作饱搏。序列抽象讓我們編寫(xiě)出這些簡(jiǎn)明的解法非剃。
將程序表達(dá)為序列操作有助于我們?cè)O(shè)計(jì)模塊化的程序。也就是說(shuō)推沸,我們的設(shè)計(jì)由組合相關(guān)的獨(dú)立片段構(gòu)建备绽,每個(gè)片段都對(duì)序列進(jìn)行轉(zhuǎn)換券坞。通常,我們可以通過(guò)提供帶有接口約定的標(biāo)準(zhǔn)組件庫(kù)來(lái)鼓勵(lì)模塊化設(shè)計(jì)肺素,接口約定以靈活的方式連接這些組件恨锚。
生成器表達(dá)式。Python 語(yǔ)言包含第二個(gè)處理序列的途徑倍靡,叫做生成器表達(dá)式猴伶。它提供了與map
和reduce
相似的功能,但是需要更少的函數(shù)定義塌西。
生成器表達(dá)式組合了過(guò)濾和映射的概念他挎,并集成于單一的表達(dá)式中,以下面的形式:
<map expression> for <name> in <sequence expression> if <filter expression>
為了求出生成器表達(dá)式捡需,Python 先求出<sequence expression>
办桨,它必須返回一個(gè)可迭代值。之后站辉,對(duì)于每個(gè)元素呢撞,按順序?qū)⒃刂到壎ǖ?code><name>,求出過(guò)濾器表達(dá)式庵寞,如果它產(chǎn)生真值狸相,就會(huì)求出映射表達(dá)式。
生成器表達(dá)式的求解結(jié)果值本身是個(gè)可迭代值捐川。累計(jì)函數(shù)脓鹃,比如tuple
、sum
古沥、max
和min
可以將返回的對(duì)象作為參數(shù)瘸右。
>>> def acronym(name):
return tuple(w[0] for w in name.split() if iscap(w))
>>> def sum_even_fibs(n):
return sum(fib(k) for k in range(1, n+1) if fib(k) % 2 == 0)
生成器表達(dá)式是使用可迭代(例如序列)接口約定的特化語(yǔ)法。這些表達(dá)式包含了map
和filter
的大部分功能岩齿,但是避免了被調(diào)用函數(shù)的實(shí)際創(chuàng)建(或者太颤,順便也避免了環(huán)境幀的創(chuàng)建需要調(diào)用這些函數(shù))。
歸約盹沈。在我們的示例中龄章,我們使用特定的函數(shù)來(lái)累計(jì)結(jié)果蔑穴,例如tuple
或者sum
咳榜。函數(shù)式編程語(yǔ)言(包括 Python)包含通用的高階累加器,具有多種名稱(chēng)实抡。Python 在functools
模塊中包含reduce
肃晚,它對(duì)序列中的元素從左到右依次調(diào)用二元函數(shù)锚贱,將序列歸約為一個(gè)值。下面的表達(dá)式計(jì)算了五個(gè)因數(shù)的積关串。
>>> from operator import mul
>>> from functools import reduce
>>> reduce(mul, (1, 2, 3, 4, 5))
120
使用這個(gè)更普遍的累計(jì)形式拧廊,除了求和之外监徘,我們也可以計(jì)算斐波那契數(shù)列中奇數(shù)的積,將序列用作接口約定吧碾。
>>> def product_even_fibs(n):
"""Return the product of the first n even Fibonacci numbers, except 0."""
return reduce(mul, filter(iseven, map(fib, range(2, n+1))))
>>> product_even_fibs(20)
123476336640
與map
凰盔、filter
和reduce
對(duì)應(yīng)的高階過(guò)程的組合會(huì)再一次在第四章出現(xiàn),在我們思考多臺(tái)計(jì)算機(jī)之間的分布式計(jì)算方法的時(shí)候滤港。