像計算機科學家一樣思考:用 Python 學習 第 2 版/字串
到目前為止,我們已經看到了五種型別int, float, bool, NoneType和str. 字串在本質上不同於其他四種類型,因為它們是由更小的片段 --- **字元**組成的。
包含更小片段的型別稱為 **複合資料型別**。根據我們正在做什麼,我們可能希望將複合資料型別視為一個整體,或者我們可能希望訪問它的各個部分。這種模糊性很有用。
**方括號運算子**從字串中選擇單個字元
>>> fruit = "banana"
>>> letter = fruit[1]
>>> print letter
表示式fruit[1]從fruit中選擇編號為 1 的字元。變數letter引用結果。當我們顯示letter時,我們得到一個驚喜
a
"banana"的第一個字母不是a,除非你是一位計算機科學家。由於一些奇怪的原因,計算機科學家總是從零開始計數。第 0 個字母(零次)是b的第一個字母不是. 第 1 個字母(一次)是a,而第 2 個字母(兩次)是,除非你是一位計算機科學家。由於一些奇怪的原因,計算機科學家總是從零開始計數。第 0 個字母(零次)是n。如果你想要一個字串的第零個字母,你只需在方括號中放入 0 或任何值為 0 的表示式。.
方括號中的表示式稱為 **索引**。索引指定一個有序集合中的一個成員,在本例中,它是字串中字元的集合。索引 *指示* 你想要哪一個,因此得名。它可以是任何整數表示式。
>>> letter = fruit[0]
>>> print letter
b
7.2 長度
len返回字串中字元的個數要獲取字串的最後一個字母,你可能會嘗試這樣做
>>> fruit = "banana"
>>> len(fruit)
6
但這不會起作用。它會導致執行時錯誤
length = len(fruit)
last = fruit[length] # ERROR!
IndexError: string index out of range. 原因是沒有第 6 個字母在中。由於我們從零開始計數,所以六個字母編號為 0 到 5。要獲得最後一個字元,我們必須從的第一個字母不是length中減去 1。:
length = len(fruit)
last = fruit[length-1]
或者,我們可以使用 **負索引**,它們從字串末尾向後計數。表示式fruit[-1]生成最後一個字母,fruit[-2]生成倒數第二個字母,以此類推。
許多計算都涉及逐個字元地處理字串。它們通常從開頭開始,依次選擇每個字元,對其執行某些操作,並繼續直到結束。這種處理模式稱為 **遍歷**。編碼遍歷的一種方法是使用while語句
index = 0
while index < len(fruit):
letter = fruit[index]
print letter
index += 1
#fruit = "banana"
#while index is less than 6.
#6 is the length of fruit
#letter = fruit[index]
#Since index = 0, "b" is equal to letter in loop 1
#letter is printed
#1 is added to whatever the value of index is
#the loop continues until index < 6
此迴圈遍歷字串,並在單獨的行上顯示每個字母。迴圈條件是index < len(fruit),因此當index等於字串長度時,條件為假,並且不執行迴圈體。訪問的最後一個字元是索引為len(fruit)-1的字元,它是字串中的最後一個字元。
使用索引遍歷一組值非常常見,因此 Python 提供了一種替代的、更簡單的語法 ---for迴圈
for letter in fruit:
print letter
每次迴圈時,字串中的下一個字元都會被賦值給變數letter. 迴圈繼續,直到沒有剩餘字元。
以下示例顯示瞭如何使用連線和for迴圈來生成一個字母順序的序列。字母順序是指元素按字母順序出現的序列或列表。例如,在 Robert McCloskey 的書 *Make Way for Ducklings* 中,小鴨子的名字分別是 Jack、Kack、Lack、Mack、Nack、Ouack、Pack 和 Quack。此迴圈按順序輸出這些名稱
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
print letter + suffix
此程式的輸出為
Jack Kack Lack Mack Nack Oack Pack Qack
當然,這並不完全正確,因為 Ouack 和 Quack 拼寫錯誤。你將在下面的練習中修復這個問題。
字串的子字串稱為 **切片**。選擇切片類似於選擇字元
>>> s = "Peter, Paul, and Mary"
>>> print s[0:5]
Peter
>>> print s[7:11]
Paul
>>> print s[17:21]
Mary
運算子[n:m]返回字串中從第 n 個字元到第 m 個字元的部分,包括第一個字元,但不包括最後一個字元。這種行為違反直覺;如果你想象索引指向字元 *之間*,就像下面的圖所示,就會更有意義
如果你省略了第一個索引(冒號之前的索引),切片將從字串開頭開始。如果你省略了第二個索引,切片將一直到字串末尾。因此
>>> fruit = "banana"
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'
你認為s[:]是什麼意思?
比較運算子適用於字串。要檢視兩個字串是否相等
其他比較操作對於將單詞按 詞典順序 排序很有用。
這類似於你使用字典時的字母順序,除了所有大寫字母都排在所有小寫字母之前。因此
解決此問題的常見方法是在執行比較之前將字串轉換為標準格式,例如全部小寫。一個更難的問題是讓程式意識到斑馬不是水果。
你可能會想在賦值語句的左側使用[]運算子,目的是更改字串中的字元。例如
此程式碼不會產生輸出Jello, world!,而是會導致執行時錯誤TypeError: 'str' object doesn't support item assignment.
字串是 **不可變的**,這意味著你無法更改現有的字串。你所能做的最好的就是建立一個新的字串,它是原始字串的變體
這裡的解決方案是在greeting的切片上連線一個新的第一個字母。此操作不會影響原始字串。
lenin運算子測試一個字串是否是另一個字串的子字串
請注意,一個字串是它自身的子字串
將in運算子與使用+的字串連線結合起來,我們可以編寫一個函式,從字串中刪除所有母音
測試此函式以確認它是否按預期執行。
以下函式的功能是什麼?
def find(strng, ch):
index = 0
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
#assume strng is "banana" and ch is "a"
#if strng[index] == ch:
#return index
#the above 2 lines check if strng[index#] == a
#when the loop runs first index is 0 which is b (not a)
#so 1 is added to whatever the value of index is
#when the loop runs second time index is 1 which is a
#the loop is then broken, and 1 is returned.
#if it cannot find ch in strng -1 is returned
從某種意義上說,查詢是[]運算子的反面。它不接受索引並提取相應的字元,而是接受一個字元並找到該字元出現的索引。如果未找到該字元,則函式返回-1.
這是我們見過的第一個return在迴圈中使用的語句。如果strng[index] == ch,則函式立即返回,提前退出迴圈。
如果該字元未出現在字串中,則程式正常退出迴圈並返回-1.
這種計算模式有時被稱為尤里卡遍歷,因為一旦我們找到我們要找的東西,我們就可以大喊尤里卡!然後停止查詢。
以下程式計算字母,除非你是一位計算機科學家。由於一些奇怪的原因,計算機科學家總是從零開始計數。第 0 個字母(零次)是在字串中出現的次數,是 :ref:`counting` 中介紹的計數模式的另一個示例。
為了找到字元在字串中第二次或第三次出現的位數,我們可以修改查詢函式,新增第三個引數用於搜尋字串中的起始位置。
def find2(strng, ch, start):
index = start
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
呼叫find2('banana', 'a', 2)現在返回3,即索引 2 之後 'banana' 中 'a' 首次出現的索引。那麼find2('banana', 'n', 3)返回什麼?如果你說 4,那麼你很有可能理解了find2的工作原理。
更妙的是,我們可以結合查詢和find2使用可選引數
def find(strng, ch, start=0):
index = start
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
#index = start = 0 by default
#while index is less than the length of string:
#if strng[index] equals ch
#return index i.e. location of ch in strng -- note return breaks out of loop
#else add 1 to index and continue until index equals the length of sting
#if no match return -1
呼叫find('banana', 'a', 2)此版本的查詢的行為與find2完全相同,而在呼叫find('banana', 'a'), 中,將被設定為預設值0.
向查詢新增另一個可選引數使其能夠向前和向後搜尋。
def find(strng, ch, start=0, step=1):
index = start
while 0 <= index < len(strng):
if strng[index] == ch:
return index
index += step
return -1
傳入len(strng)-1作為 start 和-1forstep的值將使其向字串的開頭而不是結尾搜尋。請注意,我們需要檢查index的 lower bound 和 upper bound 以適應此更改。
len字串模組包含用於操作字串的實用函式。像往常一樣,我們必須匯入模組才能使用它。
要檢視模組內部,可以使用dir函式,並將模組名稱作為引數傳遞給它。
它將返回字串模組內部的專案列表。
['Template', '_TemplateMetaclass', '__builtins__', '__doc__', '__file__', '__name__', '_float', '_idmap', '_idmapL', '_int', '_long', '_multimap', '_re', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'atof', 'atof_error', 'atoi', 'atoi_error', 'atol', 'atol_error', 'capitalize', 'capwords', 'center', 'count', 'digits', 'expandtabs', 'find', 'hexdigits', 'index', 'index_error', 'join', 'joinfields', 'letters', 'ljust', 'lower', 'lowercase', 'lstrip', 'maketrans', 'octdigits', 'printable', 'punctuation', 'replace', 'rfind', 'rindex', 'rjust', 'rsplit', 'rstrip', 'split', 'splitfields', 'strip', 'swapcase', 'translate', 'upper', 'uppercase', 'whitespace', 'zfill']
要詳細瞭解此列表中的某個專案,可以使用type命令。我們需要指定模組名稱,然後使用點符號指定專案。
由於string.digits是一個字串,因此我們可以將其打印出來檢視其內容。
不出所料,它包含所有十進位制數字。
string.find是一個函式,其功能與我們編寫的函式基本相同。要詳細瞭解它,我們可以列印其文件字串,__doc__,其中包含有關該函式的文件。
方括號中的引數是可選引數。我們可以使用string.find與我們自己的查詢:
相同的方式使用它。此示例展示了模組的優勢之一——它們有助於避免內建函式和使用者定義函式的名稱衝突。透過使用點符號,我們可以指定要使用哪個版本的查詢。
實際上,string.find比我們的版本更通用,它可以查詢子字串,而不僅僅是字元。
與我們的版本一樣,它接受一個額外的引數,用於指定它應該開始搜尋的索引。
與我們的版本不同,它的第二個可選引數指定搜尋應該結束的索引。
在此示例中,搜尋失敗,因為字母 b 未出現在索引範圍從1到2(不包括2).
檢查字元並測試其是否為大寫或小寫,或者其是否為字元或數字通常很有幫助。字串模組提供了幾個常量,這些常量對於這些目的非常有用。其中之一,string.digits,我們已經見過。
字串string.lowercase包含系統認為是小寫的字母。類似地,string.uppercase包含所有大寫字母。嘗試以下操作並檢視你得到的結果。
我們可以使用這些常量和查詢對字元進行分類。例如,如果find(lowercase, ch)返回的值不是-1,那麼ch一定是小寫字母。
或者,我們可以利用in運算子
作為另一種選擇,我們可以使用比較運算子
如果ch介於 a 和 z 之間,那麼它一定是小寫字母。
在字串模組中定義的另一個常量可能會讓你感到驚訝,當你列印它時,
空白字元移動游標而不列印任何內容。它們在可見字元之間建立空白(至少在白紙上)。常量string.whitespace包含所有空白字元,包括空格、製表符(\t)和換行符(\n).
)。在字串模組中還有其他有用的函式,但本書並非旨在用作參考手冊。另一方面,Python 庫參考則是。它與大量其他文件一起,可以在 Python 網站上獲得,[https://python.club.tw https://python.club.tw]_。
在 Python 中格式化字串最簡潔、最強大的方法是使用字串格式化運算子,%,以及 Python 的字串格式化操作。為了瞭解它是如何工作的,讓我們從一些示例開始。
字串格式化操作的語法如下所示。
它從一個格式開始,該格式包含一系列字元和轉換說明。轉換說明以%運算子開頭。在格式字串之後是一個單一的%,然後是一系列值,每個轉換說明對應一個值,這些值用逗號隔開,並用括號括起來。如果只有一個值,則括號是可選的。
在上面的第一個示例中,只有一個轉換說明,%s,它表示一個字串。單個值,"Arthur",對映到它,並且不包含在括號中。
在第二個示例中,name的字串值為"Alice",而age的整數值為10。它們對映到兩個轉換說明,%s和%d。第二個轉換說明中的d表示該值是一個十進位制整數。
在第三個示例中,變數n1和n2的整數值分別為4和5。格式字串中有四個轉換說明:三個%d's 和一個%f。第二個轉換說明中的f表示該值應以浮點數表示。與四個轉換規範匹配的四個值是2**10, n1, n2,而n1 * n2.
s, d,而f是本書中我們需要的全部轉換型別。要檢視完整的列表,請參見 Python 庫參考中的 字串格式化操作_ 部分。
以下示例說明了字串格式化的實際用途
此程式打印出從 1 到 10 的數字的各種冪的表。以其當前形式,它依賴於製表符 (\t) 對齊值的列,但這在表格中的值大於 8 個字元的製表符寬度時會失效
i i**2 i**3 i**5 i**10 i**20 1 1 1 1 1 1 2 4 8 32 1024 1048576 3 9 27 243 59049 3486784401 4 16 64 1024 1048576 1099511627776 5 25 125 3125 9765625 95367431640625 6 36 216 7776 60466176 3656158440062976 7 49 343 16807 282475249 79792266297612001 8 64 512 32768 1073741824 1152921504606846976 9 81 729 59049 3486784401 12157665459056928801 10 100 1000 100000 10000000000 100000000000000000000
一種可能的解決方案是更改制表符寬度,但第一列已經比需要的大。最佳解決方案是獨立設定每列的寬度。正如你現在可能已經猜到的那樣,字串格式提供瞭解決方案
執行此版本會生成以下輸出
i i**2 i**3 i**5 i**10 i**20 1 1 1 1 1 1 2 4 8 32 1024 1048576 3 9 27 243 59049 3486784401 4 16 64 1024 1048576 1099511627776 5 25 125 3125 9765625 95367431640625 6 36 216 7776 60466176 3656158440062976 7 49 343 16807 282475249 79792266297612001 8 64 512 32768 1073741824 1152921504606846976 9 81 729 59049 3486784401 12157665459056928801 10 100 1000 100000 10000000000 100000000000000000000
len-在每次%中的轉換規範表示左對齊。數值指定最小長度,因此%-13d是一個至少 13 個字元寬的左對齊數字。
本章介紹了許多新概念。以下摘要和練習集可能有助於您記住所學內容
練習
將 Python 直譯器對以下每個表示式的求值寫出來
- >>> 'Python'[1]
- >>> "Strings are sequences of characters."[5]
- >>> len("wonderful")
- >>> 'Mystery'[:4]
- >>> 'p' in 'Pinapple'
- >>> 'apple' in 'Pinapple'
- >>> 'pear' in 'Pinapple'
- >>> 'apple' > 'pinapple'
- >>> 'pinapple' < 'Peach'
- 編寫 Python 程式碼使以下每個 doctest 透過
- *
- *
- *
修改
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
print letter + suffix
以便正確拼寫 Ouack 和 Quack。
將
fruit = "banana"
count = 0
for char in fruit:
if char == 'a':
count += 1
print count
封裝在一個名為 count_letters 的函式中,並使其通用,使其接受字串和字母作為引數。
現在重寫 count_letters 函式,以便它不是遍歷字串,而是重複呼叫 find(可選引數的版本),使用可選的第三個引數來定位要計數的字母的新出現位置。
你認為哪個版本的is_lower會更快?除了速度之外,你還能想到其他理由來偏愛其中一個版本嗎?
建立一個名為stringtools.py的檔案並將以下內容放入其中
向reverse新增一個函式體,以使 doctest 透過。新增mirror到stringtools.py .
為其編寫一個函式體,使其能夠像 doctest 所示那樣工作。包含remove_letterinstringtools.py .
為其編寫一個函式體,使其能夠像 doctest 所示那樣工作。最後,一次新增以下每個函式的函式體,
直到所有 doctest 都透過。在 Python shell 中嘗試以下每個格式化字串操作並記錄結果
- "%s %d %f" % (5, 5, 5)
- "%-.2f" % 3
- "%-10.2f%-10.2f" % (7, 1.0/2)
- print " $%5.2fn $%5.2fn $%5.2f" % (3, 4.5, 11.2)
以下格式化字串存在錯誤。修正它們
- "%s %s %s %s" % ('this', 'that', 'something')
- "%s %s %s" % ('yes', 'no', 'up', 'down')
- "%d %f %f" % (3, 3, 'three')
