Unicode HOWTO¶
- 釋出:
1.12
本 HOWTO 討論了 Python 對錶示文字資料的 Unicode 規範的支援,並解釋了人們在使用 Unicode 時常遇到的各種問題。
Unicode 簡介¶
定義¶
今天的程式需要能夠處理各種字元。應用程式通常被國際化以顯示各種使用者可選擇語言的訊息和輸出;同一個程式可能需要用英語、法語、日語、希伯來語或俄語輸出錯誤訊息。網頁內容可以用這些語言中的任何一種編寫,也可以包含各種 emoji 符號。Python 的字串型別使用 Unicode 標準來表示字元,這使得 Python 程式能夠處理所有這些不同的可能字元。
Unicode (https://www.unicode.org/) 是一個旨在列出人類語言使用的所有字元併為每個字元賦予其唯一程式碼的規範。Unicode 規範不斷修訂和更新,以新增新的語言和符號。
一個**字元**是文字中可能最小的組成部分。'A'、'B'、'C' 等都是不同的字元。'È' 和 'Í' 也是。字元因你所說的語言或上下文而異。例如,有一個表示“羅馬數字一”的字元 'Ⅰ',它與大寫字母 'I' 是分開的。它們通常看起來相同,但它們是具有不同含義的兩個不同字元。
Unicode 標準描述了字元如何由**碼點**表示。碼點值是一個介於 0 到 0x10FFFF(大約 110 萬個值,實際分配的數量小於此)之間的整數。在標準和本文件中,碼點用 U+265E
符號表示,表示值為 0x265e
(十進位制 9,822)的字元。
Unicode 標準包含許多列出字元及其對應碼點的表格
0061 'a'; LATIN SMALL LETTER A
0062 'b'; LATIN SMALL LETTER B
0063 'c'; LATIN SMALL LETTER C
...
007B '{'; LEFT CURLY BRACKET
...
2167 'Ⅷ'; ROMAN NUMERAL EIGHT
2168 'Ⅸ'; ROMAN NUMERAL NINE
...
265E '♞'; BLACK CHESS KNIGHT
265F '♟'; BLACK CHESS PAWN
...
1F600 '😀'; GRINNING FACE
1F609 '😉'; WINKING FACE
...
嚴格來說,這些定義意味著說“這是字元 U+265E
”是沒有意義的。U+265E
是一個碼點,它代表某個特定字元;在這種情況下,它代表字元“黑色國際象棋騎士”,'♞'。在非正式語境中,碼點和字元之間的這種區別有時會被忽略。
字元在螢幕或紙張上由一組圖形元素表示,這組圖形元素稱為**字形**。例如,大寫字母 A 的字形是兩個斜線和一個水平線,儘管具體細節取決於所使用的字型。大多數 Python 程式碼不需要擔心字形;確定要顯示的正確字形通常是 GUI 工具包或終端字型渲染器的任務。
編碼¶
總結上一節:Unicode 字串是一個碼點序列,碼點是 0 到 0x10FFFF
(十進位制 1,114,111)之間的數字。這個碼點序列需要以一組**程式碼單元**的形式儲存在記憶體中,然後**程式碼單元**對映到 8 位位元組。將 Unicode 字串轉換為位元組序列的規則稱為**字元編碼**,或簡稱為**編碼**。
您可能想到的第一個編碼是使用 32 位整數作為程式碼單元,然後使用 CPU 對 32 位整數的表示。在這種表示中,字串“Python”可能看起來像這樣
P y t h o n
0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
這種表示方法很簡單,但使用它會帶來一些問題。
它不可移植;不同的處理器以不同的方式排列位元組。
它非常浪費空間。在大多數文字中,大部分碼點小於 127 或小於 255,因此大量空間被
0x00
位元組佔用。上面的字串佔用 24 位元組,而 ASCII 表示只需要 6 位元組。增加的記憶體使用量無關緊要(臺式電腦有千兆位元組的記憶體,字串通常不會那麼大),但將磁碟和網路頻寬使用量增加 4 倍是不可接受的。它與現有的 C 函式(例如
strlen()
)不相容,因此需要使用一組新的寬字串函式。
因此,這種編碼很少使用,人們轉而選擇更高效和方便的其他編碼,例如 UTF-8。
UTF-8 是最常用的編碼之一,Python 常常預設使用它。UTF 代表“Unicode 轉換格式”,'8' 表示編碼中使用 8 位值。(還有 UTF-16 和 UTF-32 編碼,但它們的使用頻率低於 UTF-8。)UTF-8 使用以下規則
如果碼點小於 128,則由相應的位元組值表示。
如果碼點大於等於 128,則轉換為兩個、三個或四個位元組的序列,其中序列中的每個位元組都介於 128 和 255 之間。
UTF-8 具有以下幾個方便的特性
它可以處理任何 Unicode 碼點。
Unicode 字串被轉換為位元組序列,該序列僅在表示空字元 (U+0000) 時包含嵌入的零位元組。這意味著 UTF-8 字串可以由 C 函式(例如
strcpy()
)處理,並透過不能處理除字串結束標記以外的零位元組的協議傳送。ASCII 文字字串也是有效的 UTF-8 文字。
UTF-8 相當緊湊;大多數常用字元可以用一到兩個位元組表示。
如果位元組損壞或丟失,可以確定下一個 UTF-8 編碼碼點的開始並重新同步。隨機的 8 位資料也不太可能看起來像有效的 UTF-8。
UTF-8 是一種面向位元組的編碼。該編碼指定每個字元由一個或多個位元組的特定序列表示。這避免了在使用面向整數和字長的編碼(如 UTF-16 和 UTF-32)時可能出現的位元組序問題,在這些編碼中,位元組序列因字串編碼的硬體而異。
參考資料¶
Unicode 聯盟網站 提供字元表、詞彙表和 Unicode 規範的 PDF 版本。請準備好一些艱難的閱讀。Unicode 的起源和發展年表 也可在該網站上找到。
在 Computerphile Youtube 頻道上,Tom Scott 簡要討論了 Unicode 和 UTF-8 的歷史(9 分 36 秒)。
為了幫助理解標準,Jukka Korpela 撰寫了一本閱讀 Unicode 字元表的入門指南。
Joel Spolsky 撰寫了另一篇優秀的入門文章。如果本介紹未能讓您清楚,您應該在繼續之前嘗試閱讀這篇替代文章。
Python 的 Unicode 支援¶
既然您已經瞭解了 Unicode 的基本知識,我們可以看看 Python 的 Unicode 特性。
字串型別¶
自 Python 3.0 以來,該語言的 str
型別包含 Unicode 字元,這意味著使用 "unicode rocks!"
、'unicode rocks!'
或三引號字串語法建立的任何字串都儲存為 Unicode。
Python 原始碼的預設編碼是 UTF-8,因此您可以簡單地在字串字面量中包含 Unicode 字元
try:
with open('/tmp/input.txt', 'r') as f:
...
except OSError:
# 'File not found' error message.
print("Fichier non trouvé")
旁註:Python 3 還支援在識別符號中使用 Unicode 字元
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
f.write("test\n")
如果您無法在編輯器中輸入特定字元,或者出於某種原因希望原始碼僅包含 ASCII 字元,您也可以在字串字面量中使用轉義序列。(根據您的系統,您可能會看到實際的大寫 delta 字形而不是 u 轉義。)
>>> "\N{GREEK CAPITAL LETTER DELTA}" # Using the character name
'\u0394'
>>> "\u0394" # Using a 16-bit hex value
'\u0394'
>>> "\U00000394" # Using a 32-bit hex value
'\u0394'
此外,可以使用 bytes
的 decode()
方法建立字串。此方法接受一個 encoding 引數,例如 UTF-8
,並可選地接受一個 errors 引數。
errors 引數指定當輸入字串無法根據編碼規則轉換時的響應。此引數的合法值包括 'strict'
(引發 UnicodeDecodeError
異常)、'replace'
(使用 U+FFFD
,REPLACEMENT CHARACTER
)、'ignore'
(只從 Unicode 結果中刪除字元)或 'backslashreplace'
(插入 \xNN
轉義序列)。以下示例顯示了差異
>>> b'\x80abc'.decode("utf-8", "strict")
Traceback (most recent call last):
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'
編碼被指定為包含編碼名稱的字串。Python 提供了大約 100 種不同的編碼;有關列表,請參閱 Python 庫參考中的 標準編碼。某些編碼具有多個名稱;例如,'latin-1'
、'iso_8859_1'
和 '8859
都是相同編碼的同義詞。
單字元 Unicode 字串也可以使用內建函式 chr()
建立,該函式接受整數並返回長度為 1 的 Unicode 字串,其中包含相應的碼點。反向操作是內建函式 ord()
,它接受一個單字元 Unicode 字串並返回碼點值
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344
轉換為位元組¶
bytes.decode()
的相反方法是 str.encode()
,它返回以請求的 encoding 編碼的 Unicode 字串的 bytes
表示。
errors 引數與 decode()
方法的引數相同,但支援更多可能的處理程式。除了 'strict'
、'ignore'
和 'replace'
(在這種情況下插入問號而不是不可編碼的字元)之外,還有 'xmlcharrefreplace'
(插入 XML 字元引用)、backslashreplace
(插入 \uNNNN
轉義序列)和 namereplace
(插入 \N{...}
轉義序列)。
以下示例顯示了不同的結果
>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'
用於註冊和訪問可用編碼的低階例程在 codecs
模組中找到。實現新編碼也需要理解 codecs
模組。然而,該模組返回的編碼和解碼函式通常比使用起來更底層,並且編寫新編碼是一項專門任務,因此本 HOWTO 不會涵蓋該模組。
Python 原始碼中的 Unicode 字面量¶
在 Python 原始碼中,特定的 Unicode 碼點可以使用 \u
轉義序列編寫,後跟四個十六進位制數字,給出碼點。\U
轉義序列類似,但預期是八個十六進位制數字,而不是四個
>>> s = "a\xac\u1234\u20ac\U00008000"
... # ^^^^ two-digit hex escape
... # ^^^^^^ four-digit Unicode escape
... # ^^^^^^^^^^ eight-digit Unicode escape
>>> [ord(c) for c in s]
[97, 172, 4660, 8364, 32768]
少量使用大於 127 的碼點的轉義序列是沒問題的,但如果您使用許多重音字元,例如在用法語或其他使用重音的語言編寫的程式中,這就會變得很麻煩。您還可以使用內建函式 chr()
組裝字串,但這更加繁瑣。
理想情況下,您希望能夠用您語言的自然編碼編寫字面量。這樣您就可以用您最喜歡的編輯器編輯 Python 原始碼,它會自然地顯示重音字元,並在執行時使用正確的字元。
Python 預設支援使用 UTF-8 編寫原始碼,但如果您宣告正在使用的編碼,則可以使用幾乎任何編碼。這可以透過在原始檔的第一行或第二行包含特殊註釋來完成
#!/usr/bin/env python
# -*- coding: latin-1 -*-
u = 'abcdé'
print(ord(u[-1]))
此語法受到 Emacs 用於指定檔案本地變數的記法啟發。Emacs 支援許多不同的變數,但 Python 只支援“編碼”。-*-
符號表示 Emacs 認為此註釋是特殊的;它們對 Python 沒有意義,但只是一種約定。Python 在註釋中查詢 coding: name
或 coding=name
。
如果您不包含此類註釋,則預設使用的編碼將是 UTF-8,如前所述。另請參閱 PEP 263 以獲取更多資訊。
Unicode 屬性¶
Unicode 規範包含一個關於碼點資訊的資料庫。對於每個已定義的碼點,資訊包括字元的名稱、類別、適用的數值(例如表示羅馬數字、三分之一和五分之四等數字概念的字元)。還有與顯示相關的屬性,例如如何在雙向文字中使用碼點。
以下程式顯示了幾個字元的一些資訊,並列印了某個特定字元的數值
import unicodedata
u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)
for i, c in enumerate(u):
print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
print(unicodedata.name(c))
# Get numeric value of second character
print(unicodedata.numeric(u[1]))
執行時,它會列印
0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE
1 0bf2 No TAMIL NUMBER ONE THOUSAND
2 0f84 Mn TIBETAN MARK HALANTA
3 1770 Lo TAGBANWA LETTER SA
4 33af So SQUARE RAD OVER S SQUARED
1000.0
類別程式碼是描述字元性質的縮寫。它們被分為“字母”、“數字”、“標點符號”或“符號”等類別,這些類別又被細分為子類別。從上面的輸出中獲取程式碼,'Ll'
表示“字母,小寫”,'No'
表示“數字,其他”,'Mn'
表示“標記,非間距”,'So'
表示“符號,其他”。有關類別程式碼列表,請參閱 Unicode 字元資料庫文件的通用類別值部分。
字串比較¶
Unicode 為字串比較增加了一些複雜性,因為相同的字元集可以由不同的碼點序列表示。例如,像“ê”這樣的字母可以表示為單個碼點 U+00EA,或者表示為 U+0065 U+0302,即“e”的碼點後跟“組合抑揚符”的碼點。這些在列印時會產生相同的輸出,但一個是長度為 1 的字串,另一個是長度為 2 的字串。
用於不區分大小寫比較的工具之一是 casefold()
字串方法,它根據 Unicode 標準描述的演算法將字串轉換為不區分大小寫形式。此演算法對某些字元(例如德語字母“ß”(碼點 U+00DF))有特殊處理,它會變成小寫字母對“ss”。
>>> street = 'Gürzenichstraße'
>>> street.casefold()
'gürzenichstrasse'
第二個工具是 unicodedata
模組的 normalize()
函式,該函式將字串轉換為幾種規範形式之一,其中後面帶有組合字元的字母會被單個字元替換。normalize()
可用於執行字串比較,如果兩個字串以不同方式使用組合字元,則不會錯誤地報告不相等
import unicodedata
def compare_strs(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(s1) == NFD(s2)
single_char = 'ê'
multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print('length of first string=', len(single_char))
print('length of second string=', len(multiple_chars))
print(compare_strs(single_char, multiple_chars))
執行時,這會輸出
$ python compare-strs.py
length of first string= 1
length of second string= 2
True
normalize()
函式的第一個引數是一個字串,給出所需的規範化形式,可以是 'NFC'、'NFKC'、'NFD' 和 'NFKD' 之一。
Unicode 標準還規定了如何進行不區分大小寫的比較
import unicodedata
def compare_caseless(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())
# Example usage
single_char = 'ê'
multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print(compare_caseless(single_char, multiple_chars))
這將列印 True
。(為什麼 NFD() 被呼叫兩次?因為有一些字元會導致 casefold()
返回非規範化字串,因此結果需要再次規範化。有關討論和示例,請參閱 Unicode 標準第 3.13 節。)
Unicode 正則表示式¶
re
模組支援的正則表示式可以作為位元組或字串提供。一些特殊字元序列,例如 \d
和 \w
,根據模式是作為位元組還是字串提供而具有不同的含義。例如,\d
將匹配位元組中的字元 [0-9]
,但在字串中將匹配 'Nd'
類別中的任何字元。
此示例中的字串用泰語和阿拉伯數字寫著數字 57
import re
p = re.compile(r'\d+')
s = "Over \u0e55\u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))
執行時,\d+
將匹配泰語數字並將其打印出來。如果您向 compile()
提供 re.ASCII
標誌,\d+
將匹配子字串“57”。
同樣,\w
匹配各種 Unicode 字元,但在位元組中或提供了 re.ASCII
時只匹配 [a-zA-Z0-9_]
,而 \s
將匹配 Unicode 空白字元或 [ \t\n\r\f\v]
。
參考資料¶
一些關於 Python Unicode 支援的優秀替代討論包括
Nick Coghlan 的 Python 3 中的文字檔案處理。
Ned Batchelder 在 PyCon 2012 上的演示 實用 Unicode。
str
型別在 Python 庫參考中的 文字序列型別 — str 中描述。
unicodedata
模組的文件。
codecs
模組的文件。
Marc-André Lemburg 在 EuroPython 2002 上發表了題為“Python 和 Unicode”(PDF 幻燈片)的演講。幻燈片提供了 Python 2 Unicode 功能設計的優秀概述(其中 Unicode 字串型別稱為 unicode
,字面量以 u
開頭)。
讀取和寫入 Unicode 資料¶
一旦您編寫了一些處理 Unicode 資料的程式碼,下一個問題就是輸入/輸出。您如何將 Unicode 字串輸入到您的程式中,以及如何將 Unicode 轉換為適合儲存或傳輸的形式?
根據您的輸入源和輸出目標,您可能不需要做任何事情;您應該檢查您的應用程式中使用的庫是否原生支援 Unicode。例如,XML 解析器通常返回 Unicode 資料。許多關係資料庫也支援 Unicode 值列,並且可以從 SQL 查詢返回 Unicode 值。
Unicode 資料通常在寫入磁碟或透過套接字傳送之前轉換為特定的編碼。您可以自己完成所有工作:開啟檔案,從中讀取 8 位位元組物件,然後使用 bytes.decode(encoding)
轉換位元組。但是,不建議採用手動方法。
一個問題是編碼的多位元組性質;一個 Unicode 字元可以由幾個位元組表示。如果您想以任意大小的塊(例如,1024 或 4096 位元組)讀取檔案,則需要編寫錯誤處理程式碼來捕獲在塊末尾只讀取了單個 Unicode 字元編碼的位元組的一部分的情況。一個解決方案是將整個檔案讀入記憶體,然後執行解碼,但這會阻止您處理非常大的檔案;如果您需要讀取 2 GiB 檔案,則需要 2 GiB 記憶體。(實際上更多,因為至少有一會兒您需要同時在記憶體中儲存編碼字串及其 Unicode 版本。)
解決方案是使用低階解碼介面來捕獲部分編碼序列的情況。實現此功能的工作已經為您完成:內建函式 open()
可以返回一個檔案狀物件,該物件假定檔案內容採用指定的編碼,並接受 Unicode 引數用於 read()
和 write()
等方法。這透過 open()
的 encoding 和 errors 引數實現,它們的解釋與 str.encode()
和 bytes.decode()
中的引數相同。
因此,從檔案中讀取 Unicode 很簡單
with open('unicode.txt', encoding='utf-8') as f:
for line in f:
print(repr(line))
還可以以更新模式開啟檔案,允許讀寫
with open('test', encoding='utf-8', mode='w+') as f:
f.write('\u4500 blah blah blah\n')
f.seek(0)
print(repr(f.readline()[:1]))
Unicode 字元 U+FEFF
用作位元組序標記(BOM),通常作為檔案的第一個字元寫入,以幫助自動檢測檔案的位元組序。某些編碼(如 UTF-16)期望檔案開頭存在 BOM;當使用此類編碼時,BOM 將自動寫入為第一個字元,並在讀取檔案時靜默刪除。這些編碼有變體,例如用於小端和大端編碼的“utf-16-le”和“utf-16-be”,它們指定特定的位元組序並且不跳過 BOM。
在某些區域,也習慣在 UTF-8 編碼檔案的開頭使用“BOM”;這個名稱具有誤導性,因為 UTF-8 不依賴於位元組順序。該標記只是宣告檔案採用 UTF-8 編碼。為了讀取此類檔案,請使用“utf-8-sig”編解碼器,以便在存在時自動跳過該標記。
Unicode 檔名¶
目前常用的絕大多數作業系統都支援包含任意 Unicode 字元的檔名。通常這是透過將 Unicode 字串轉換為根據系統而異的某種編碼來實現的。如今 Python 正趨向於使用 UTF-8:macOS 上的 Python 已經使用了幾個版本的 UTF-8,Python 3.6 也開始在 Windows 上使用 UTF-8。在 Unix 系統上,只有在您設定了 LANG
或 LC_CTYPE
環境變數時才會有檔案系統編碼;如果您沒有設定,預設編碼仍然是 UTF-8。
sys.getfilesystemencoding()
函式返回當前系統上要使用的編碼,以防您想手動編碼,但沒有太多理由這樣做。開啟檔案進行讀寫時,通常只需提供 Unicode 字串作為檔名,它將自動轉換為正確的編碼
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
f.write('blah\n')
os
模組中的函式,例如 os.stat()
,也將接受 Unicode 檔名。
os.listdir()
函式返回檔名,這引出了一個問題:它應該返回檔名的 Unicode 版本,還是應該返回包含編碼版本的位元組?os.listdir()
可以同時做到這兩點,這取決於您提供的目錄路徑是位元組還是 Unicode 字串。如果您傳遞 Unicode 字串作為路徑,檔名將使用檔案系統的編碼進行解碼,並返回 Unicode 字串列表;而傳遞位元組路徑將返回位元組形式的檔名。例如,假設預設的檔案系統編碼是 UTF-8,執行以下程式
fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()
import os
print(os.listdir(b'.'))
print(os.listdir('.'))
將生成以下輸出
$ python listdir-test.py
[b'filename\xe4\x94\x80abc', ...]
['filename\u4500abc', ...]
第一個列表包含 UTF-8 編碼的檔名,第二個列表包含 Unicode 版本。
請注意,在大多數情況下,您只需堅持使用這些 API 的 Unicode 版本。位元組 API 僅應在可能存在無法解碼的檔名的系統上使用;目前這基本上僅限於 Unix 系統。
編寫支援 Unicode 程式的技巧¶
本節提供了一些關於編寫處理 Unicode 軟體的建議。
最重要的提示是
軟體內部應僅使用 Unicode 字串,儘快解碼輸入資料,並且只在最後才編碼輸出。
如果您嘗試編寫既接受 Unicode 字串又接受位元組字串的處理函式,您會發現您的程式在組合這兩種不同型別的字串的任何地方都容易受到錯誤的影響。沒有自動編碼或解碼:如果您執行例如 str + bytes
,將引發 TypeError
。
當使用來自 Web 瀏覽器或其他不受信任源的資料時,一種常見技術是在字串用於生成的命令列或儲存在資料庫中之前檢查字串中的非法字元。如果您正在這樣做,請務必檢查已解碼的字串,而不是編碼的位元組資料;某些編碼可能具有有趣的屬性,例如不是雙射或不完全與 ASCII 相容。如果輸入資料還指定了編碼,則尤其如此,因為攻擊者可以選擇巧妙的方法在編碼的位元組流中隱藏惡意文字。
檔案編碼之間的轉換¶
StreamRecoder
類可以透明地在編碼之間進行轉換,它接受一個以編碼 #1 返回資料的流,並表現得像一個以編碼 #2 返回資料的流。
例如,如果您有一個以 Latin-1 編碼的輸入檔案 f,您可以用 StreamRecoder
將其包裝,以返回 UTF-8 編碼的位元組
new_f = codecs.StreamRecoder(f,
# en/decoder: used by read() to encode its results and
# by write() to decode its input.
codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),
# reader/writer: used to read and write to the stream.
codecs.getreader('latin-1'), codecs.getwriter('latin-1') )
未知編碼的檔案¶
如果您需要修改一個檔案,但不知道檔案的編碼怎麼辦?如果您知道編碼與 ASCII 相容,並且只想檢查或修改 ASCII 部分,您可以使用 surrogateescape
錯誤處理程式開啟檔案
with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
data = f.read()
# make changes to the string 'data'
with open(fname + '.new', 'w',
encoding="ascii", errors="surrogateescape") as f:
f.write(data)
surrogateescape
錯誤處理程式會將任何非 ASCII 位元組解碼為特殊範圍 U+DC80 到 U+DCFF 中的碼點。當使用 surrogateescape
錯誤處理程式編碼資料並將其寫回時,這些碼點將再次變回相同的位元組。
參考資料¶
David Beazley 在 PyCon 2010 上的演講 掌握 Python 3 輸入/輸出 的一個部分討論了文字處理和二進位制資料處理。
Marc-André Lemburg 演講 “在 Python 中編寫支援 Unicode 的應用程式”的 PDF 幻燈片 討論了字元編碼問題以及如何國際化和本地化應用程式。這些幻燈片僅涵蓋 Python 2.x。
Python 中 Unicode 的內部機制 是 Benjamin Peterson 在 PyCon 2013 上的演講,討論了 Python 3.3 中 Unicode 的內部表示。
致謝¶
本文件的初稿由 Andrew Kuchling 撰寫。此後,Alexander Belopolsky、Georg Brandl、Andrew Kuchling 和 Ezio Melotti 對其進行了進一步修訂。
感謝以下人員指出錯誤或為此文章提供了建議:Éric Araujo、Nicholas Bastin、Nick Coghlan、Marius Gedminas、Kent Johnson、Ken Krugler、Marc-André Lemburg、Martin von Löwis、Terry J. Reedy、Serhiy Storchaka、Eryk Sun、Chad Whitacre、Graham Wideman。