Python 字串處理是程式開發的基礎,理解其運作原理對於有效處理文字資料至關重要。本文從字串的基本操作開始,逐步探討索引、切片、連線等技巧,並解析如何避免常見的 TypeErrorIndexError。此外,文章也涵蓋了 Unicode 處理的相關知識,說明如何在 Python 中正確讀取、寫入和顯示 Unicode 文字,以及如何使用 codecsunicodedata 模組來處理不同編碼的文字。最後,文章也簡要介紹了正規表示式和 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。

練習與挑戰

  1. 試著寫一個Python程式來統計一段文字中每個字母的頻率,並忽略大小寫和非字母字元。
  2. 使用切片符號來提取一段文字中的特定部分,例如提取句子中的某個詞或短語。

這些練習將幫助你更好地理解和掌握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 文字進行分詞處理。