摘要:序列不是特定的抽象數(shù)據(jù)類(lèi)型,而是不同類(lèi)型共有的一組行為。不像抽象數(shù)據(jù)類(lèi)型,我們并沒(méi)有闡述如何構(gòu)造序列。這兩個(gè)選擇器和一個(gè)構(gòu)造器,以及一個(gè)常量共同實(shí)現(xiàn)了抽象數(shù)據(jù)類(lèi)型的遞歸列表。
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)?,F(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)始的子列表?,F(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á)式
對(duì)于序列中的每個(gè)元素值,按順序:
在局部環(huán)境中將變量名
執(zhí)行語(yǔ)句組
步驟 1 引用了可迭代的值。序列是可迭代的,它們的元素可看做迭代的順序。Python 的確擁有其他可迭代類(lèi)型,但是我們現(xiàn)在只關(guān)注序列。術(shù)語(yǔ)“可迭代對(duì)象”的一般定義會(huì)在第四章的迭代器一節(jié)中出現(xiàn)。
這個(gè)求值過(guò)程的一個(gè)重要結(jié)果是,在for語(yǔ)句執(zhí)行完畢之后,
序列解構(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í)觀察到了這一點(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)有多帶帶的字符類(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 claims, "Readability counts." Read more: import this."
在上面的打印結(jié)果中, (叫做“反斜杠加 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á)式中,以下面的形式:
為了求出生成器表達(dá)式,Python 先求出
生成器表達(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í)候。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/38119.html
摘要:函數(shù)體由表達(dá)式組成。我們說(shuō)頭部控制語(yǔ)句組。于是,函數(shù)體內(nèi)的賦值語(yǔ)句不會(huì)影響全局幀。包含了多種假值,包括和布爾值。布爾值表示了邏輯表達(dá)式中的真值。執(zhí)行測(cè)試以及返回布爾值的函數(shù)通常以開(kāi)頭,并不帶下劃線(xiàn)例如等等。返回值之后會(huì)和預(yù)期結(jié)果進(jìn)行比對(duì)。 1.5 控制 來(lái)源:1.5 Control 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 我們現(xiàn)在可以定義的函數(shù)能力有限,因?yàn)槲覀冞€不知...
摘要:計(jì)算器語(yǔ)言解釋器的核心是叫做的遞歸函數(shù),它會(huì)求解樹(shù)形表達(dá)式對(duì)象。到目前為止,我們?cè)诿枋銮笾颠^(guò)程中所引用的表達(dá)式樹(shù),還是概念上的實(shí)體。解析器實(shí)際上由兩個(gè)組件組成,詞法分析器和語(yǔ)法分析器。標(biāo)記序列由叫做的詞法分析器產(chǎn)生,并被叫做語(yǔ)法分析器使用。 3.5 組合語(yǔ)言的解釋器 來(lái)源:3.5 Interpreters for Languages with Combination 譯者:飛龍 ...
摘要:遞歸列表可以使用遞歸函數(shù)最為自然地操作,就像它們的名稱(chēng)和結(jié)構(gòu)表示的那樣。處理遞歸列表遞歸列表結(jié)構(gòu)將列表表示為首個(gè)元素和列表的剩余部分的組合。例如,我們可以使用高階遞歸函數(shù)將樹(shù)的每個(gè)葉子平方,它的結(jié)構(gòu)類(lèi)似于。成員測(cè)試會(huì)遞歸遍歷整個(gè)列表。 3.3 遞歸數(shù)據(jù)結(jié)構(gòu) 來(lái)源:3.3 Recursive Data Structures 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 在第二...
摘要:到目前為止,我們的環(huán)境只包含全局幀。要注意函數(shù)名稱(chēng)是重復(fù)的,一個(gè)在幀中,另一個(gè)是函數(shù)的一部分。運(yùn)算符字表達(dá)式是全局幀中發(fā)現(xiàn)的名稱(chēng),綁定到了內(nèi)建的加法函數(shù)上。嚴(yán)格來(lái)說(shuō),這并不是問(wèn)題所在不同局部幀中的的綁定是不相關(guān)的。 1.3 定義新的函數(shù) 來(lái)源:1.3 Defining New Functions 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 我們已經(jīng)在 Python 中認(rèn)識(shí)...
摘要:消息向迭代器獲取所表示的底層序列的下一個(gè)元素。為了對(duì)方法調(diào)用做出回應(yīng),迭代器可以執(zhí)行任何計(jì)算來(lái)獲取或計(jì)算底層數(shù)據(jù)序列的下一個(gè)元素。這個(gè)迭代器應(yīng)擁有方法,依次返回序列中的每個(gè)元素,最后到達(dá)序列末尾時(shí)產(chǎn)生異常。 第五章 序列和協(xié)程 來(lái)源:Chapter 5: Sequences and Coroutines 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 5.1 引言 在這一章中,我...
閱讀 1833·2021-11-18 13:21
閱讀 1966·2021-10-18 13:30
閱讀 1551·2021-10-12 10:13
閱讀 922·2021-10-09 09:43
閱讀 5436·2021-09-22 15:13
閱讀 3595·2021-08-11 10:22
閱讀 947·2019-08-30 13:46
閱讀 3527·2019-08-30 13:21