Python 字串處理是程式開發的基礎,理解其運作原理對於有效處理文字資料至關重要。本文從字串的基本操作開始,逐步探討索引、切片、連線等技巧,並解析如何避免常見的 TypeError
和 IndexError
。此外,文章也涵蓋了 Unicode 處理的相關知識,說明如何在 Python 中正確讀取、寫入和顯示 Unicode 文字,以及如何使用 codecs
和 unicodedata
模組來處理不同編碼的文字。最後,文章也簡要介紹了正規表示式和 NLTK 在 Unicode 文書處理中的應用,提供更進階的文書處理技巧。
字串處理基礎
在Python中,字串是一種基本資料型別,用於表示文字資訊。字串可以被視為字元的序列,並且支援多種操作,如索引、切片、連線和搜尋等。
字串的基本操作
當我們試圖對字串進行不適當的操作時,Python會丟擲錯誤。例如,嘗試對兩個字串進行減法或將字串除以整數,都會導致TypeError
。
>>> 'very' - 'y'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> 'very' / 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
這些錯誤訊息告訴我們,減法和除法運算無法應用於字串。
列印字串
我們可以使用print
陳述式來顯示變數的內容或計算結果。
>>> monty = 'Monty Python'
>>> print(monty)
Monty Python
注意,這裡沒有引號,因為print
陳述式直接輸出字串的內容。
字串連線
我們可以使用+
運算元來連線兩個字串。
>>> grail = 'Holy Grail'
>>> print(monty + grail)
Monty PythonHoly Grail
或者使用逗號分隔多個變數或字串,print
陳述式會自動在它們之間加上空格。
>>> print(monty, grail)
Monty Python Holy Grail
>>> print(monty, "and the", grail)
Monty Python and the Holy Grail
存取個別字元
與列表類別似,字串是可索引的,從0開始索引。當我們索引一個字串時,我們得到它的其中一個字元。
>>> monty[0]
'M'
>>> monty[3]
't'
>>> monty[5]
' '
內容解密:
monty[0]
傳回字串的第一個字元 ‘M’。monty[3]
傳回字串的第四個字元 ’t’。monty[5]
傳回字串的第六個字元,一個空格。
如果我們嘗試存取超出字串範圍的索引,會得到一個錯誤。
>>> monty[20]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
我們也可以使用負索引,-1代表最後一個字元。
>>> monty[-1]
'n'
>>> monty[-7]
' '
內容解密:
monty[-1]
傳回字串的最後一個字元 ’n’。monty[-7]
傳回從結尾數來的第七個字元,一個空格。
迭代字元
我們可以寫一個for
迴圈來迭代字串中的字元。
>>> sent = 'colorless green ideas sleep furiously'
>>> for char in sent:
... print(char, end=' ')
c o l o r l e s s g r e e n i d e a s s l e e p f u r i o u s l y
內容解密:
for char in sent
迭代sent
字串中的每個字元。print(char, end=' ')
列印每個字元,後面跟一個空格,而不是換行符。
統計字元頻率
我們可以統計文字中字元的頻率,並忽略大小寫和非字母字元。
>>> from nltk.corpus import gutenberg
>>> raw = gutenberg.raw('melville-moby_dick.txt')
>>> fdist = nltk.FreqDist(ch.lower() for ch in raw if ch.isalpha())
>>> fdist.keys()
['e', 't', 'a', 'o', 'n', 'i', 's', 'h', 'r', 'l', 'd', 'u', 'm', 'c', 'w', 'f', 'g', 'p', 'b', 'y', 'v', 'k', 'q', 'j', 'x', 'z']
內容解密:
ch.lower()
將所有字元轉換為小寫,以忽略大小寫差異。if ch.isalpha()
過濾掉非字母字元,只保留字母。nltk.FreqDist
統計每個字母的頻率,並按頻率排序。
存取子字串
子字串是字串中連續的一部分,可以使用切片符號來存取。
>>> monty[6:10]
'Pyth'
這裡,我們取得從索引6開始到索引10(不包含)的子字串。
內容解密:
monty[6:10]
傳回從第六個字元到第九個字元的子字串 ‘Pyth’。
我們也可以省略起始或結束索引,分別代表從頭開始或到結尾結束。
>>> monty[:5]
'Monty'
>>> monty[6:]
'Python'
內容解密:
monty[:5]
傳回從開始到第五個字元的子字串 ‘Monty’。monty[6:]
傳回從第六個字元到結尾的子字串 ‘Python’。
檢查子字串是否存在
我們可以使用in
運算元來檢查一個子字串是否存在於另一個字串中。
>>> phrase = 'And now for something completely different'
>>> if 'thing' in phrase:
... print('found "thing"')
found "thing"
內容解密:
'thing' in phrase
檢查 ’thing’ 是否存在於phrase
中。
查詢子字串的位置
我們可以使用find()
方法來查詢子字串在字串中的位置。
>>> monty.find('Python')
6
內容解密:
monty.find('Python')
傳回 ‘Python’ 在monty
中的起始索引,即6。
練習與挑戰
- 試著寫一個Python程式來統計一段文字中每個字母的頻率,並忽略大小寫和非字母字元。
- 使用切片符號來提取一段文字中的特定部分,例如提取句子中的某個詞或短語。
這些練習將幫助你更好地理解和掌握Python中的字串操作,為進一步的文字處理和分析打下堅實的基礎。
更多字串操作
Python 對字串處理有全面的支援。表 3-2 總結了一些我們尚未見過的字串操作。若需要更多關於字串的資訊,請在 Python 命令列中輸入 help(str)
。
表 3-2:實用的字串方法
這些是除了表 1-4 中所示的字串測試之外對字串的操作;所有方法都會產生新的字串或列表。
方法 | 功能描述 |
---|---|
s.find(t) | 字串 t 在 s 中首次出現的索引(未找到則傳回 -1) |
s.rfind(t) | 字串 t 在 s 中最後一次出現的索引(未找到則傳回 -1) |
s.index(t) | 與 s.find(t) 類別似,但未找到時會引發 ValueError |
s.rindex(t) | 與 s.rfind(t) 類別似,但未找到時會引發 ValueError |
s.join(text) | 使用 s 作為連線符,將 text 中的單片語合成一個字串 |
s.split(t) | 在 s 中每當出現 t 時進行分割(預設為空白字元)並傳回一個列表 |
s.splitlines() | 將 s 按行分割成一個字串列表 |
s.lower() | 傳回 s 的小寫版本 |
s.upper() | 傳回 s 的大寫版本 |
s.titlecase() | 傳回 s 的標題大小寫版本 |
s.strip() | 傳回去除 s 首尾空白字元的副本 |
s.replace(t, u) | 將 s 中的 t 替換為 u |
清單與字串的差異
字串和清單都是序列的一種。我們可以透過索引和切片來存取它們的元素,也可以透過連線來組合它們。然而,我們無法直接將字串和清單連線起來:
>>> query = 'Who knows?'
>>> beatles = ['John', 'Paul', 'George', 'Ringo']
>>> query[2]
'o'
>>> beatles[2]
'George'
>>> query[:2]
'Wh'
>>> beatles[:2]
['John', 'Paul']
>>> query + " I don't"
"Who knows? I don't"
>>> beatles + 'Brian'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>> beatles + ['Brian']
['John', 'Paul', 'George', 'Ringo', 'Brian']
當我們開啟一個檔案並讀取其內容到 Python 程式中時,我們得到的是對應於整個檔案內容的字串。如果我們使用 for
迴圈來處理這個字串的元素,我們只能逐一存取個別的字元,無法選擇粒度。相比之下,清單的元素大小可以任意調整:例如,它們可以是段落、句子、短語、單詞或字元。因此,清單的優勢在於我們可以靈活地控制其包含的元素,並對其進行靈活的後續處理。因此,在 NLP 程式碼中,我們通常做的第一件事就是將字串標記化為字串清單(第 3.7 節)。相反,當我們想要將結果寫入檔案或終端時,通常會將其格式化為字串(第 3.9 節)。
清單和字串的功能不完全相同。清單具有額外的功能,可以更改其元素:
>>> beatles[0] = "John Lennon"
>>> del beatles[-1]
>>> beatles
['John Lennon', 'Paul', 'George']
另一方面,如果我們嘗試對字串進行相同的操作——將 query
的第 0 個字元更改為 'F'
——我們會得到:
>>> query[0] = 'F'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: object does not support item assignment
這是因為字串是不可變的:一旦建立,就無法更改。然而,清單是可變的,其內容可以隨時修改。因此,清單支援修改原始值的操作,而不是產生新的值。
內容解密:
上述程式碼展示了清單與字串之間的基本操作差異。清單支援元素的修改和刪除,而字串則不行。這是因為清單是可變的,而字串是不可變的。這些特性對於選擇適合的資料結構來處理文字資料非常重要。
使用 Unicode 處理文字
我們的程式通常需要處理不同的語言和不同的字元集。“純文字”的概念是一種理想化。如果您生活在英語世界中,您可能使用 ASCII,甚至沒有意識到這一點。如果您生活在歐洲,您可能會使用擴充套件的拉丁字元集,其中包含諸如丹麥語和挪威語的 “ø”、匈牙利語的 “ő”、西班牙語和布列塔尼語的 “ñ”、捷克語和斯洛伐克語的 “ň” 等字元。在本文中,我們將概述如何使用 Unicode 處理使用非 ASCII 字元集的文字。
Unicode 是什麼?
Unicode 支援超過一百萬個字元。每個字元都被分配一個稱為程式碼點的編號。在 Python 中,程式碼點以 \uXXXX
的形式表示,其中 XXXX
是四位十六進位制數字。
在程式內部,我們可以像操作普通字串一樣操作 Unicode 字串。然而,當 Unicode 字元被儲存在檔案中或顯示在終端上時,它們必須被編碼為位元組流。一些編碼方式(如 ASCII 和 Latin-2)每個程式碼點使用一個位元組,因此它們只能支援 Unicode 的一小部分,足夠用於單一語言。其他編碼方式(如 UTF-8)使用多個位元組,可以表示完整的 Unicode 字元範圍。
圖表說明:
此圖示展示了 Unicode 編碼與解碼的過程。文字資料首先被解碼為 Unicode,然後根據需要編碼為特定的位元組表示形式以便儲存或傳輸。
檔案中的文字將採用特定的編碼,因此我們需要某種機制將其翻譯成 Unicode——這種翻譯成 Unicode 的過程稱為解碼。相反,要將 Unicode 輸出到檔案或終端,我們首先需要將其翻譯成合適的編碼——這種從 Unicode 出來的翻譯稱為編碼,如圖 3-3 所示。
從 Unicode 的角度來看,字元是抽象的實體,可以被實作為一個或多個圖形。只有圖形才能顯示在螢幕上或列印在紙上。字型是字元到圖形的對映。
從檔案中提取編碼文字
假設我們有一個小型文字檔案,並且知道它的編碼方式。例如,polish-lat2.txt
從檔名來看,是一段波蘭語文字(來自波蘭維基百科;參見 http://pl.wikipedia.org/wiki/Biblioteka_Pruska)。該檔案以 Latin-2 編碼,也稱為 ISO-8859-2。函式 nltk.data.find()
為我們定位了檔案。
import nltk
nltk.data.find('path/to/polish-lat2.txt')
內容解密:
上述程式碼展示瞭如何使用 nltk.data.find()
函式來定位特定的文字檔案。這是在進行多語言文書處理時常見的一個步驟,確保我們能夠正確地找到並讀取所需的檔案。
隨著 NLP 技術的不斷進步,對文字資料的處理和分析變得越來越複雜。未來,我們可能會看到更多先進的技術和方法被開發出來,以應對日益增長的大規模文字資料處理需求。同時,Unicode 的支援也將繼續擴充套件,以涵蓋更多語言和字元集。
Unicode 在文書處理中的應用
在處理多語言文字時,Unicode 成為了一種至關重要的編碼方式。本章節將探討 Unicode 在 Python 中的應用,以及如何有效地處理 Unicode 文字。
使用 Python 的 codecs
模組讀取 Unicode 文字
Python 的 codecs
模組提供了一種便捷的方式來讀取和寫入不同編碼的檔案。以下是一個範例,展示如何使用 codecs.open()
函式以 ’latin2’ 編碼讀取波蘭檔案案:
>>> import codecs
>>> path = nltk.data.find('corpora/unicode_samples/polish-lat2.txt')
>>> f = codecs.open(path, encoding='latin2')
內容解密:
codecs.open()
函式允許我們指設定檔案的編碼格式,這裡使用 ’latin2’ 編碼來讀取波蘭檔案案。- 讀取的文字會被轉換成 Unicode 編碼,以便於在 Python 中進行處理。
將 Unicode 文字寫入檔案
我們也可以使用 codecs.open()
函式將 Unicode 文字寫入檔案。以下是一個範例,展示如何以 ‘utf-8’ 編碼寫入檔案:
>>> f = codecs.open(path, 'w', encoding='utf-8')
內容解密:
- 在寫入檔案時,指定
encoding='utf-8'
可以確保 Unicode 文字被正確地編碼。 - ‘utf-8’ 編碼是一種廣泛使用的編碼方式,能夠支援大多數語言的文字。
在終端機上顯示 Unicode 文字
當我們嘗試在終端機上顯示 Unicode 文字時,可能會遇到編碼問題。以下是一個範例,展示如何使用 unicode_escape
編碼來顯示 Unicode 文字:
>>> for line in f:
... line = line.strip()
... print(line.encode('unicode_escape'))
內容解密:
line.encode('unicode_escape')
將 Unicode 文字編碼成 ‘unicode_escape’ 格式,這樣可以避免編碼錯誤。- 這種方式特別適合用於除錯和檢查 Unicode 文字的內容。
使用 unicodedata
模組檢查 Unicode 字元的屬性
Python 的 unicodedata
模組提供了一種方式來檢查 Unicode 字元的屬性。以下是一個範例,展示如何使用 unicodedata.name()
函式來取得 Unicode 字元的名稱:
>>> import unicodedata
>>> lines = codecs.open(path, encoding='latin2').readlines()
>>> line = lines[2]
>>> for c in line:
... if ord(c) > 127:
... print('%r U+%04x %s' % (c.encode('utf8'), ord(c), unicodedata.name(c)))
內容解密:
unicodedata.name(c)
傳回 Unicode 字元c
的名稱。- 這種方式可以用於檢查和理解特定 Unicode 字元的屬性和含義。
在 Python 中使用本地編碼
如果你習慣使用特定的本地編碼,可以在 Python 檔案中加入 # -*- coding: <coding> -*-
這一行來指定編碼。這樣可以讓你在 Python 中直接使用本地編碼的文字。
# -*- coding: utf-8 -*-
內容解密:
- 這行註解告訴 Python 直譯器使用指定的編碼來讀取檔案。
- 這樣可以避免因為編碼不匹配而導致的錯誤。
正規表示式在 Unicode 文書處理中的應用
正規表示式是一種強大的工具,可以用於在 Unicode 文字中進行模式匹配。以下是一個範例,展示如何在 Python 中使用正規表示式來搜尋 Unicode 文字:
>>> import re
>>> line = lines[2].lower()
>>> m = re.search(u'\u015b\w*', line)
>>> m.group()
內容解密:
re.search(u'\u015b\w*', line)
使用正規表示式在line
中搜尋以\u015b
開頭的單詞。- 這種方式可以用於在 Unicode 文字中進行複雜的模式匹配。
NLTK 中的 Unicode 支援
NLTK(Natural Language Toolkit)提供了對 Unicode 文字的良好支援。以下是一個範例,展示如何使用 NLTK 的 word_tokenize()
函式來對 Unicode 文字進行分詞:
>>> import nltk
>>> nltk.word_tokenize(line)
內容解密:
nltk.word_tokenize(line)
將line
中的文字分割成單詞。- 這種方式可以用於對 Unicode 文字進行分詞處理。