詳解Python中的文字處理
字串 -- 不可改變的序列
如同大多數高階程式語言一樣,變長字串是 Python 中的基本型別。Python 在“後臺”分配記憶體以儲存字串(或其它值),程式設計師不必為此操心。Python 還有一些其它高階語言沒有的字串處理功能。
在 Python 中,字串是“不可改變的序列”。儘管不能“按位置”修改字串(如位元組組),但程式可以引用字串的元素或子序列,就象使用任何序列一樣。Python 使用靈活的“分片”操作來引用子序列,字元片段的格式類似於電子表格中一定範圍的行或列。以下互動式會話說明了字串和字元片段的的用法:
字串和分片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
>>> s =
"mary had a little lamb"
>>> s[ 0 ]
# index is zero-based
'm'
>>> s[ 3 ] =
'x'
# changing element in-place fails
Traceback (innermost last):
File
"<stdin>" , line 1 ,
in
?
TypeError:
object doesn't support item assignment
>>> s[ 11 : 18 ]
# 'slice' a subsequence 'little '
>>> s[: 4 ]
# empty slice-begin assumes zero
'mary'
>>> s[ 4 ]
# index 4 is not included in slice [:4]
' '
>>> s[ 5 : - 5 ]
# can use "from end" index with negatives
'had a little'
>>> s[: 5 ] + s[ 5 :]
# slice-begin & slice-end are complimentary
'mary had a little lamb' |
另一個功能強大的字串操作就是簡單的 in 關鍵字。它提供了兩個直觀有效的構造:
in 關鍵字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
>>> s =
"mary had a little lamb"
>>>
for
c
in
s[ 11 : 18 ]:
print
c,
# print each char in slice
...
l i t t l e
>>>
if
'x'
in
s:
print
'got x'
# test for char occurrence
...
>>>
if
'y'
in
s:
print
'got y'
# test for char occurrence
...
got y
|
在 Python 中,有幾種方法可以構成字串文字。可以使用單引號或雙引號,只要左引號和右引號匹配,常用的還有其它引號的變化形式。如果字串包含換行符或嵌入引號,三重引號可以很方便地定義這樣的字串,如下例所示:
三重引號的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> s2 =
"""Mary had a little lamb
... its fleece was white as snow
... and everywhere that Mary went
... the lamb was sure to go"""
>>>
print
s2
Mary had a little lamb
its fleece was white as snow
and
everywhere that Mary went
the lamb was sure to go
|
使用單引號或三重引號的字串前面可以加一個字母 "r" 以表示 Python 不應該解釋規則表示式特殊字元。例如:
使用 "r-strings"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>>> s3 =
"this \n and \n that"
>>>
print
s3
this
and
that
>>> s4 = r
"this \n and \n that"
>>>
print
s4
this \n
and
\n that
|
在 "r-strings" 中,可能另外組成換碼符的反斜槓被當作是常規反斜槓。在以後的規則表示式討論中會進一步說明這個話題。
檔案和字串變數
我們談到“文字處理”時,我們通常是指處理的內容。Python 將文字檔案的內容讀入可以操作的字串變數非常容易。檔案物件提供了三個“讀”方法: .read()、.readline() 和 .readlines()。每種方法可以接受一個變數以限制每次讀取的資料量,但它們通常不使用變數。 .read() 每次讀取整個檔案,它通常用於將檔案內容放到一個字串變數中。然而 .read() 生成檔案內容最直接的字串表示,但對於連續的面向行的處理,它卻是不必要的,並且如果檔案大於可用記憶體,則不可能實現這種處理。
.readline() 和 .readlines() 非常相似。它們都在類似於以下的結構中使用:
Python .readlines() 示例
1 2 3 4 5 6 7 8 9 |
fh = open (
'c:\\autoexec.bat' )
for
line
in
fh.readlines():
print
line
|
.readline() 和 .readlines() 之間的差異是後者一次讀取整個檔案,象 .read() 一樣。.readlines() 自動將檔案內容分析成一個行的列表,該列表可以由 Python 的 for ... in ... 結構進行處理。另一方面,.readline() 每次只讀取一行,通常比 .readlines() 慢得多。僅當沒有足夠記憶體可以一次讀取整個檔案時,才應該使用 .readline()。
如果正在使用處理檔案的標準模組,可以使用 cStringIO 模組將字串轉換成“虛擬檔案”(如果需要生成模組的子類,可以使用 StringIO 模組,初學者未必要這樣做)。例如:
cStringIO 模組
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>>
import
cStringIO
>>> fh = cStringIO.StringIO()
>>> fh.write(
"mary had a little lamb" )
>>> fh.getvalue()
'mary had a little lamb'
>>> fh.seek( 5 )
>>> fh.write(
'ATE' )
>>> fh.getvalue()
'mary ATE a little lamb'
|
但是,請記住,cStringIO“虛擬檔案”不是永久的,這一點與真正的檔案不同。如果不儲存它(如將它寫入一個真正的檔案,或者使用 shelve 模組或資料庫),則程式結束時,它將消失。
標準模組:string
string 模組也許是 Python 1.5.* 標準發行版中最常用的模組。實際上,在 Python 1.6 或更高版本中,string 模組中的功能將作為內建字串方法(在撰寫本文時,詳細資訊尚未釋出)。當然,任何執行文字處理任務的程式也許應該用以下這行開頭:
開始使用 string 的方法
import string
一般經驗法則告訴我們,如果 可以 使用 string 模組完成任務,那麼那就是 正確 的方法。與 re(規則表示式)相比,string 函式通常更快速,大多數情況下他們更易於理解和維護。第三方 Python 模組,包括某些用 C 編寫的快速模組,適用於專門的任務,但可移植性和熟悉性都建議只要可能就使用 string。如果您習慣於使用其它語言,也會有例外,但不如您想像的那樣多。
string 模組包含了幾種型別的事物,如函式、方法和類;它還包含了公共常量的字串。例如:
string 用法例 1
1 2 3 4 5 6 7 |
>>>
import
string
>>> string.whitespace
'\011\012\013\014\015 '
>>> string.uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
雖然可以用手寫出這些常量,string 版本或多或少確保了常量對於執行 Python 指令碼的國家語言和平臺將是正確的。
string 還包括了以常見方式(可以結合這些方式來構成幾種罕見的轉換)轉換字串的函式。例如:
string 用法例 2
1 2 3 4 5 6 7 8 9 10 11 |
>>>
import
string
>>> s =
"mary had a little lamb"
>>> string.capwords(s)
'Mary Had A Little Lamb'
>>> string.replace(s,
'little' ,
'ferocious' )
'mary had a ferocious lamb'
|
還有許多沒有在這裡具體說明的其它轉換;可以在 Python 手冊中查詢詳細資訊。
還可以使用 string 函式來報告字串屬性,如子串的長度或位置,例如:
string 用法例 3
1 2 3 4 5 6 7 8 |
>>>
import
string
>>> s =
"mary had a little lamb"
>>> string.find(s,
'had' ) 5 >>> string.count(s,
'a' ) 4
|
最後,string 提供了非常 Python 化的奇特事物。.split() 和 .join() 對提供了在字串和位元組組之間轉換的迅捷方法,您會發現它們非常有用。用法很簡單:
string 用法例 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>>
import
string>>> s =
"mary had a little lamb"
>>> L = string.split(s)
>>> L
[
'mary' ,
'had' ,
'a' ,
'little' ,
'lamb' ]
>>> string.join(L,
"-" )
'mary-had-a-little-lamb'
|
當然,除了 .join() 之外,也許會利用列表來做其它事(如某些涉及我們熟悉的 for ... in ... 結構的事情)。
標準模組:re
re 模組廢棄了在老的 Python 程式碼中使用的 regex 和 regsub 模組。雖然相對於 regex 仍然有幾個有限的優點,不過這些優點微不足道,不值得在新程式碼中使用。過時的模組可能會從未來的 Python 發行版中刪除,並且 1.6 版可能有一個改進的介面相容的 re 模組。所以,規則表示式仍將使用 re 模組。
規則表示式很複雜。也許有人會撰寫關於這個主題的書,但實際上,已經有許多人這樣做了!本文嘗試捕捉規則表示式的“完全形態”,讓讀者可以掌握它。
規則表示式是一種很簡練方法,用於描述可能在文字中出現的模式。是否會出現某些字元?是否按特定順序出現?子模式是否會重複一定次數?其它子模式是否會排除在匹配之外?從概念上說,似乎不能用自然語言了直觀地描述模式。訣竅是使用規則表示式的簡潔語法來編碼這種描述。
當處理規則表示式時,將它作為它自己的程式設計問題來處理,即使只涉及一或兩行程式碼;這些行有效地構成了一個小程式。
從最小處著手。從最基本上看,任何規則表示式都涉及匹配特定的“字元類”。最簡單的字元類就是單個字元,它在模式中只是一個字。通常,您希望匹配一類字元。可以通過將類括在方括號內來表明這是一個類;在括號中,可以有一組字元或者用破折號指定的字元範圍。還可以使用許多命名字元類來確定您的平臺和國家語言。以下是一些示例:
字元類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
>>>
import
re
>>> s =
"mary had a little lamb"
>>>
if
re.search(
"m" , s):
print
"Match!"
# char literal
Match!
>>>
if
re.search(
"[@A-Z]" , s):
print
"Match!"
# char class
...
# match either at-sign or capital letter
...
>>>
if
re.search(
"\d" , s):
print
"Match!"
# digits class
...
|
可以將字元類看作是規則表示式的“原子”,通常會將那些原子組合成“分子”。可以結合使用 分組和 迴圈 來完成此操作。由括號表示分組:括號中包含的任何子表示式都被看作是用於以後分組或迴圈的原子。迴圈則由以下幾個運算子中的某一個來表示:"*" 表示“零或多”;"+" 表示“一或多”;"?" 表示“零或一”。例如,請看以下示例:
樣本規則表示式
ABC([d-w]*\d\d?)+XYZ
對於要匹配這個表示式的字串,它必須以 "ABC" 開頭、以 "XYZ" 結尾 -- 但它的中間必須要有什麼呢?中間子表示式是 ([d-w]*\d\d?),而且後面跟了“一或多”運算子。所以,字串的中間必須包括一個(或者兩個,或者一千個)與括號中的子表示式匹配的字元或字串。字串 "ABCXYZ" 不匹配,因為它的中間沒有必要的字元。
不過這個內部子表示式是什麼呢?它以 d-w 範圍內的 零或多個 字母開頭。一定要注意:零字母是有效匹配,雖然使用英語單詞 "some"(一些)來描述它,可能會感到很彆扭。接著,字串必須 恰好有一個數字;然後有 零或一個 附加數字。(第一個數字字元類沒有迴圈運算子,所以它只出現一次。第二個數字字元類有 "?" 運算子。)總而言之,這將翻譯成“一個或兩個數字”。以下是一些與規則表示式匹配的字串:
匹配樣本表示式的字串
1 2 3 |
ABC1234567890XYZ
ABCd12e1f37g3XYZ
ABC1XYZ
|
還有一些表示式與規則表示式 不匹配(想一想,它們為什麼不匹配):
不匹配樣本表示式的字串
1 2 3 4 5 |
ABC123456789dXYZ
ABCdefghijklmnopqrstuvwXYZ
ABcd12e1f37g3XYZ
ABC12345 % 67890XYZ
ABCD12E1F37G3XYZ
|
需要一些練習才能習慣建立和理解規則表示式。但是,一旦掌握了規則表示式,您就具有了強大的表達能力。也就是說,轉而使用規則表示式解決問題通常會很容易,而這類問題實際上可以使用更簡單(而且更快速)的工具,如 string,來解決。