跳轉到內容

Think Python/字串

來自華夏公益教科書,開放的書籍,為開放的世界

字串是一個序列

[編輯 | 編輯原始碼]

字串是序列字元。你可以使用括號運算子一次訪問一個字元

>>> fruit = 'banana'
>>> letter = fruit[1]

第二個語句從fruit中選擇第1個字元並將其賦值給letter

括號中的表示式稱為索引。索引指示您想要序列中的哪個字元(因此得名)。

但是您可能不會得到您期望的結果

>>> print letter
a

對大多數人來說,'banana'的第一個字母是b,而不是a。但對於計算機科學家來說,索引是字串開頭的偏移量,第一個字母的偏移量為零。

>>> letter = fruit[0]
>>> print letter
b

所以b'banana'的第0個字母(“零次”),a是第1個字母(“一次”),n是第2個字母(“二次”)。

您可以使用任何表示式(包括變數和運算子)作為索引,但索引的值必須是整數。否則你會得到

>>> letter = fruit[1.5]
TypeError: string indices must be integers

len是一個內建函式,它返回字串中的字元數

>>> fruit = 'banana'
>>> len(fruit)
6

要獲得字串的最後一個字母,您可能很想嘗試以下方法

>>> length = len(fruit)
>>> last = fruit[length]
IndexError: string index out of range

IndexError的原因是'banana'中沒有索引為6的字母。因為我們從零開始計數,所以六個字母編號為0到5。要獲取最後一個字元,您必須從length中減去1

>>> last = fruit[length-1]
>>> print last
a

或者,您可以使用負索引,它們從字串末尾反向計數。表示式fruit[-1]生成最後一個字母,fruit[-2]生成倒數第二個字母,依此類推。

使用 for 迴圈遍歷

[編輯 | 編輯原始碼]

許多計算涉及逐個字元處理字串。它們通常從開頭開始,依次選擇每個字元,對其進行處理,並繼續到結尾。這種處理模式稱為遍歷。使用while迴圈是編寫遍歷的一種方法

index = 0
while index < len(fruit):
    letter = fruit[index]
    print letter
    index = index + 1

此迴圈遍歷字串並在單獨的行上顯示每個字母。迴圈條件是index < len(fruit),所以當index等於字串的長度時,條件為假,並且不執行迴圈體。訪問的最後一個字元是索引為len(fruit)-1的字元,它是字串中的最後一個字元。

練習 1    編寫一個函式,該函式以字串為引數,並將字母反向顯示,每行一個字母。

使用for迴圈是編寫遍歷的另一種方法

for char in fruit:
    print char

每次迴圈時,字串中的下一個字元都會被分配給變數char。迴圈繼續,直到沒有字元剩餘。

以下示例展示瞭如何使用連線(字串加法)和for迴圈生成一個按字母順序排列的字母表系列(即按字母順序排列)。在羅伯特·麥克洛斯基的書《為小鴨子讓路》中,小鴨子的名字是傑克、凱克、萊克、麥克、納克、奧克、帕克和夸克。此迴圈按順序輸出這些名稱

prefixes = 'JKLMNOPQ'
suffix = 'ack'

for letter in prefixes:
    print letter + suffix

輸出為

Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack

當然,這不太正確,因為“Ouack”和“Quack”拼寫錯誤。

修改程式以修復此錯誤。

字串切片

[編輯 | 編輯原始碼]

字串的一部分稱為切片。選擇切片類似於選擇字元

>>> s = 'Monty Python'
>>> print s[0:5]
Monty
>>> print s[6:13]
Python

運算子[n:m]返回字串從“第n個”字元到“第m個”字元的部分,包括第一個但不包括最後一個。這種行為違反直覺,但想象索引指向字元之間可能會有所幫助,如下面的圖所示

<IMG SRC="book011.png">

如果您省略第一個索引(冒號之前),則切片從字串開頭開始。如果您省略第二個索引,則切片將一直到字串結尾

>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'

如果第一個索引大於或等於第二個索引,則結果為空字串,用兩個引號表示

>>> fruit = 'banana'
>>> fruit[3:3]

空字串不包含任何字元,長度為0,但除此之外,它與任何其他字串相同。

假設'fruit'是一個字串,'fruit[:]'是什麼意思?

字串是不可變的

[編輯 | 編輯原始碼]

您可能會很想在賦值的左側使用[]運算子,目的是更改字串中的字元。例如

>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: object does not support item assignment

在這種情況下,“物件”是字串,“專案”是您嘗試分配的字元。目前,“物件”與值相同,但稍後我們將完善該定義。“專案”是序列中的一個值。

發生錯誤的原因是字串是不可變的,這意味著您無法更改現有字串。您能做的最好的事就是建立一個新的字串,它是原始字串的變化形式

>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> print new_greeting
Jello, world!

此示例將一個新的第一個字母連線到greeting的切片上。它對原始字串沒有影響。

以下函式有什麼作用?

def find(word, letter):
    index = 0
    while index &lt; len(word):
        if word[index] == letter:
            return index
        index = index + 1
    return -1

在某種意義上,find[]運算子相反。它不是接受一個索引並提取相應的字元,而是接受一個字元並查詢該字元出現的索引。如果字元未找到,該函式返回-1

這是我們見到的第一個在迴圈內使用return語句的示例。如果word[index] == letter,則函式會退出迴圈並立即返回。

如果字元沒有出現在字串中,則程式會正常退出迴圈並返回-1

這種計算模式——遍歷一個序列並在找到我們正在尋找的東西時返回——被稱為搜尋

修改 'find' 使其具有第三個引數,即 'word' 中應開始查詢的索引。

迴圈和計數

[編輯 | 編輯原始碼]

以下程式計算字母 a 在字串中出現的次數。

word = 'banana'
count = 0
for letter in word:
    if letter == 'a':
        count = count + 1
print count

該程式演示了另一種稱為計數器的計算模式。變數 count 初始化為 0,然後每次找到一個 a 時就增加 1。當迴圈退出時,count 包含結果——a 的總數。

練習 5  將此程式碼封裝到一個名為 'count' 的函式中,並將其泛化,使其接受字串和字母作為引數。
練習 6  重寫此函式,使其不遍歷字串,而是使用上一節中的 'find' 的三引數版本。

string 方法

[編輯 | 編輯原始碼]

方法類似於函式——它接收引數並返回一個值——但語法不同。例如,方法 upper 接收一個字串並返回一個所有字母都大寫的新字串。

它不使用函式語法 upper(word),而是使用方法語法 word.upper()

>>> word = 'banana'
>>> new_word = word.upper()
>>> print new_word
BANANA

這種點符號形式指定了方法的名稱 upper 和要應用方法的字串的名稱 word。空括號表示此方法不接收任何引數。

方法呼叫稱為呼叫;在這種情況下,我們可以說我們正在對 word 呼叫 upper

事實證明,存在一個名為 find 的字串方法,它與我們編寫的函式非常相似。

>>> word = 'banana'
>>> index = word.find('a')
>>> print index
1

在此示例中,我們對 word 呼叫 find 並將我們正在尋找的字母作為引數傳遞。

實際上,find 方法比我們的函式更通用;它可以查詢子字串,而不僅僅是字元。

>>> word.find('na')
2

它可以作為第二個引數接收它應該開始查詢的索引。


>>> word.find('na', 3)
4

以及作為第三個引數接收它應該停止查詢的索引。

>>> name = 'bob'
>>> name.find('b', 1, 2)
-1

此搜尋失敗,因為 b 未出現在從 12(不包括 2)的索引範圍內。

練習 7  存在一個名為 'count' 的字串方法,它類似於上一個練習中的函式。閱讀此方法的文件並編寫一個呼叫,該呼叫計算 banana 中 'a' 的數量。

in 運算子

[編輯 | 編輯原始碼]

單詞 in 是一個布林運算子,它接收兩個字串,如果第一個字串作為子字串出現在第二個字串中,則返回 True

>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False

例如,以下函式列印來自 word1 的所有也出現在 word2 中的字母。

def in_both(word1, word2):
    for letter in word1:
        if letter in word2:
            print letter

使用精心選擇的變數名,Python 有時讀起來像英語。您可以閱讀此迴圈,“對於 (每個) 字母在 (第一個) 單詞中,如果 (該) 字母 (出現在) (第二個) 單詞中,則列印 (該) 字母。”

以下是在比較蘋果和橘子時得到的結果。

>>> in_both('apples', 'oranges')
a
e
s

字串比較

[編輯 | 編輯原始碼]

比較運算子適用於字串。要檢視兩個字串是否相等。

if word == 'banana':
    print  'All right, bananas.'

其他比較運算對於將單詞按字母順序排列很有用。

if word < 'banana':
    print 'Your word,' + word + ', comes before banana.'
elif word > 'banana':
    print 'Your word,' + word + ', comes after banana.'
else:
    print 'All right, bananas.'

Python 處理大寫和小寫字母的方式與人們不同。所有大寫字母都位於所有小寫字母之前,因此。

Your word, Pineapple, comes before banana.

解決此問題的常用方法是在執行比較之前將字串轉換為標準格式,例如全部小寫。如果您必須為自己辯護以對抗拿著菠蘿的男子,請牢記這一點。

當您使用索引遍歷序列中的值時,很難正確地確定遍歷的開始和結束。以下函式應該比較兩個單詞,如果其中一個單詞是另一個單詞的反轉,則返回 True,但它包含兩個錯誤。

def is_reverse(word1, word2):
    if len(word1) != len(word2):
        return False
    
    i = 0
    j = len(word2)

    while j > 0:
        if word1[i] != word2[j]:
            return False
        i = i+1
        j = j-1

    return True

第一個 if 語句檢查單詞的長度是否相同。如果不是,我們可以立即返回 False,然後,對於函式的其餘部分,我們可以假設單詞的長度相同。這是第 6.8 節中的守護模式的一個示例。

ij 是索引:i 正向遍歷 word1,而 j 反向遍歷 word2。如果我們找到兩個不匹配的字母,我們可以立即返回 False。如果我們遍歷整個迴圈並且所有字母都匹配,則返回 True

如果我們用單詞“pots”和“stop”測試此函式,我們期望返回值 True,但我們得到一個 IndexError。


>>> is_reverse('pots', 'stop')
...
  File "reverse.py", line 15, in is_reverse
    if word1[i] != word2[j]:
IndexError: string index out of range

為了除錯這種錯誤,我的第一步是在錯誤出現的行之前立即列印索引的值。

    while j > 0:
        print i, j        # print here
        
        if word1[i] != word2[j]:
            return False
        i = i+1
        j = j-1

現在,當我再次執行程式時,我獲得了更多資訊。

>>> is_reverse('pots', 'stop')
0 4
...
IndexError: string index out of range

第一次遍歷迴圈時,j 的值為 4,這對於字串 'pots' 來說超出了範圍。最後一個字元的索引是 3,因此 j 的初始值應該是 len(word2)-1



如果我修復了該錯誤並再次執行程式,我得到。

>>> is_reverse('pots', 'stop')
0 3
1 2
2 1
True

這次我們得到了正確的答案,但看起來迴圈只運行了三次,這很可疑。為了更好地瞭解正在發生的事情,繪製狀態圖很有用。在第一次迭代期間,is_reverse 的幀如下所示。


<IMG SRC="book012.png">

我透過排列幀中的變數並新增虛線來稍微擴充套件了一點,以顯示 ij 的值指示 word1word2 中的字元。

練習 8  從該圖開始,在紙上執行程式,在每次迭代期間更改 'i' 和 'j' 的值。找到並修復此函式中的第二個錯誤。

詞彙表

[編輯 | 編輯原始碼]
物件
變數可以引用的東西。目前,您可以互換使用“物件”和“值”。
序列
一個有序集合;也就是說,一組值,其中每個值都由一個整型索引標識。
專案
序列中的一個值。
索引
用於選擇序列中專案的整型值,例如字串中的字元。
切片
由一組索引指定的部分字串。
空字串
沒有字元且長度為 0 的字串,用兩個引號表示。
不可變
無法分配其專案的序列的屬性。
遍歷
迭代序列中的專案,對每個專案執行類似的操作。
搜尋
一種遍歷模式,它在找到正在尋找的東西時停止。
計數器
用於統計某物的變數,通常初始化為零,然後遞增。
方法
與物件相關聯的函式,使用點符號呼叫。
呼叫
呼叫方法的語句。

字串切片可以接受第三個索引,該索引指定“步長”;也就是說,連續字元之間的空格數。

步長為 2 表示隔一個字元;3 表示隔兩個字元,依此類推。

 >>> fruit = 'banana'
>>> fruit[0:5:2]
'bnn'

步長為 -1 表示向後遍歷單詞,因此切片 [::-1] 生成反轉的字串。

使用此習慣用法編寫 is_palindrome 的單行版本,來自練習 '6.6'

練習 10

[編輯 | 編輯原始碼]

閱讀 'docs.python.org/lib/string-methods.html' 中字串方法的文件。您可能需要嘗試其中的一些方法,以確保您瞭解其工作原理。'strip' 和 'replace' 特別有用。

文件使用了一種可能令人困惑的語法。例如,在 find(sub[, start[, end]]) 中,方括號表示可選引數。所以 'sub' 是必需的,但 'start' 是可選的,如果您包含 'start',那麼 'end' 是可選的。

練習 11

[編輯 | 編輯原始碼]

以下函式都旨在檢查字串是否包含任何小寫字母,但至少其中一些是錯誤的。對於每個函式,描述函式的實際功能。

def any_lowercase1(s):
    for c in s:
        if c.islower():
            return True
        else:
            return False

def any_lowercase2(s):
    for c in s:
        if 'c'.islower():
            return 'True'
        else:
            return 'False'

def any_lowercase3(s):
    for c in s:
        flag = c.islower()
    return flag

def any_lowercase4(s):
    flag = False
    for c in s:
        flag = flag or c.islower()
    return flag

def any_lowercase5(s):
    for c in s:
        if not c.islower():
            return False
    return True

練習 12

[編輯 | 編輯原始碼]

ROT13 是一種弱加密形式,它涉及將單詞中的每個字母“旋轉”13 個位置[1]。旋轉字母意味著在字母表中移動它,必要時環繞到開頭,因此 'A' 向右移 3 個位置變為 'D','Z' 向右移 1 個位置變為 'A'。

編寫一個名為rotate_word的函式,該函式以字串和整數為引數,並返回一個新字串,該字串包含原始字串中“旋轉”指定數量的字母。

例如,“cheer” 旋轉 7 個位置變為 “jolly”,而 “melon” 旋轉 -10 個位置變為 “cubed”。

你可能想要使用內建函式 'ord'(將字元轉換為數字程式碼)和 'chr'(將數字程式碼轉換為字元)。

網際網路上可能冒犯性的笑話有時會用 ROT13 編碼。如果你不容易被冒犯,請找到並解碼一些這樣的笑話。

  1. 參見 wikipedia.org/wiki/ROT13
華夏公益教科書