SICP Python 描述 2.3 序列

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的局部幀县恕,它包含綁定到相同子列表的sfirst函數(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í)行:

  1. 求出頭部表達(dá)式<expression>,它必須產(chǎn)生一個(gè)可迭代的值莉炉。
  2. 對(duì)于序列中的每個(gè)元素值钓账,按順序:
    1. 在局部環(huán)境中將變量名<name>綁定到這個(gè)值上。
    2. 執(zhí)行語(yǔ)句組<suite>絮宁。

步驟 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)xy分別綁定到每個(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è)操作符innot in捎泻,取決于元素是否在序列中出現(xiàn)而求值為TrueFalse飒炎。

>>> digits
(1, 8, 2, 8)
>>> 2 in digits
True
>>> 1828 not in digits
True

所有序列都有叫做indexcount的方法,它會(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

與之相似锅睛,字符串上的countindex方法接受子串作為參數(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)芦劣。

  1. 對(duì)前n個(gè)斐波那契數(shù)中的偶數(shù)求和。
  2. 列出一個(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

mapfilter函數(shù)是序列操作怜姿,我們已經(jīng)見(jiàn)過(guò)了map,它在序列中的每個(gè)元素上調(diào)用函數(shù)并且收集結(jié)果疼燥。filter函數(shù)接受序列沧卢,并且返回序列中謂詞為真的元素。兩個(gè)函數(shù)都返回間接對(duì)象醉者,mapfilter對(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)題的解立磁,使用mapfiltersum剥槐。

>>> 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)粒竖,包含mapfilter颅崩。

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ù)可以使用mapfilter定義。

>>> 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á)式猴伶。它提供了與mapreduce相似的功能,但是需要更少的函數(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ù)脓鹃,比如tuplesum古沥、maxmin可以將返回的對(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á)式包含了mapfilter的大部分功能岩齿,但是避免了被調(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凰盔、filterreduce對(duì)應(yīng)的高階過(guò)程的組合會(huì)再一次在第四章出現(xiàn),在我們思考多臺(tái)計(jì)算機(jī)之間的分布式計(jì)算方法的時(shí)候滤港。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廊蜒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子溅漾,更是在濱河造成了極大的恐慌山叮,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件添履,死亡現(xiàn)場(chǎng)離奇詭異屁倔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)暮胧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)锐借,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人往衷,你說(shuō)我怎么就攤上這事钞翔。” “怎么了席舍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵布轿,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我来颤,道長(zhǎng)汰扭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任福铅,我火速辦了婚禮萝毛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘滑黔。我一直安慰自己笆包,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布略荡。 她就那樣靜靜地躺著庵佣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撞芍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天跨扮,我揣著相機(jī)與錄音序无,去河邊找鬼验毡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帝嗡,可吹牛的內(nèi)容都是我干的晶通。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哟玷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼狮辽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起巢寡,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喉脖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后抑月,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體树叽,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年谦絮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了题诵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡层皱,死狀恐怖性锭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叫胖,我是刑警寧澤草冈,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站臭家,受9級(jí)特大地震影響疲陕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钉赁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一蹄殃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧你踩,春花似錦诅岩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至膝藕,卻和暖如春式廷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芭挽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工滑废, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝗肪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓蠕趁,卻偏偏與公主長(zhǎng)得像薛闪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俺陋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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