前言

3. 檔案的開啟與關閉 介紹了open()的檔案名稱與模式兩個參數。open()其實還有第三個常用參數 —— 編碼 (encoding)

open(檔案名稱 [, 模式] [, 編碼])

open函式全部有 8 個參數,最常使用的是檔案名稱、模式和編碼參數,其中只有第一個檔案名稱是不可省略,其他的參數都可以省略,省略時會使用預設值。

中文資料在讀寫檔案時最容易出問題,而問題幾乎都出在編碼上。本節說明編碼的選擇與常見錯誤。

編碼參數

指定檔案的編碼模式,一般可設定cp950UTF-8大小寫都可以)。

編碼說明
cp950繁體中文 Windows 的預設編碼,也就是記事本所稱的 ANSI
UTF-8國際通行的編碼,Linux/macOS 與多數程式的預設
UTF-8-sigUTF-8 但會自動處理 BOM(見後面說明)

如果是用 Spyder 儲存的檔案,預設編碼為UTF-8;若是繁體中文 Windows 系統,編碼依作業系統而定,預設編碼是cp950

可以在.py程式中用下列程式取得目前作業系統設定的編碼:

import locale
print(locale.getpreferredencoding())

省略 encoding 會怎樣

在中文 Windows 系統中開啟一個純文字檔,用「記事本」編輯的話,預設是用 ANSI 編碼儲存,因此可以使用下列語法開啟 ANSI 編碼的檔案:

f = open('file1.txt', 'r')                          # 使用系統預設編碼
f = open('file1.txt', 'r', encoding='cp950')        # 明確指定 cp950

不指定 encoding 是「不可攜」的寫法

省略encoding時,Python 會使用作業系統的預設編碼 —— 這代表同一支程式在你的電腦跑得好好的,換到同學的電腦(或 Linux 主機)可能就亂碼。
建議一律明確寫出encoding='UTF-8',不要依賴系統預設。

編碼不符會發生什麼事

如果使用encoding='cp950'去讀取 UTF-8 編碼的檔案,顯示資料內容會出現錯誤。

[簡例] 使用 cp950 開啟已另存為 UTF-8 編碼格式的<file2.txt>檔並顯示資料內容。(檔名:filereadUTF-8.py

[結果]

UnicodeDecodeError: 'cp950' codec can't decode byte 0xbf in position 2:
illegal multibyte sequence

必須將encoding指定為UTF-8才可順利讀取和顯示:

f = open('file2.txt', 'r', encoding='UTF-8')     # ✅

由於國際間通行的編碼以及許多 Linux 系統預設都是使用 UTF-8 編碼,因此 建議將檔案另存為 UTF-8(不要使用 ANSI)

認得這個錯誤訊息

錯誤意義通常原因
UnicodeDecodeError的時候解不開讀檔的 encoding 跟檔案實際編碼不符
UnicodeEncodeError的時候轉不出去要寫的字元該編碼不支援(例如用 cp950 寫 emoji)

看到 Decode 就往「讀檔」找,看到 Encode 就往「寫檔/輸出」找。

BOM 的處理

什麼是 BOM

BOM (Byte Order Mark) 是一個特殊字元,功能是用來標示文件的編碼。BOM 是在 Windows 系統中用「記事本」將檔案儲存為 UTF-8 時會自動產生的。

[簡例] 讀取 UTF-8 編碼的<file2.txt>檔案的文件內容。(檔名:fileread6.py

[結果]

['123中文字\n', 'abcde\n', 'hello\n']
123中

有沒有注意到,串列內容資料最前面多了一個「」字元,這個字元就是 BOM。

BOM 會佔 1 個字元,因此f.read(5)的結果只看到「123中」這 4 個字,因為第一個字元 BOM 未顯示出來。

實測驗證:

utf-8-sig 寫出的檔案前 3 bytes: b'\xef\xbb\xbf'
utf-8     寫出的檔案前 3 bytes: b'123'
 
用 utf-8     讀有 BOM 的檔 -> '123中文字\n'
用 utf-8-sig 讀有 BOM 的檔 -> '123中文字\n'

BOM 最麻煩的地方是「看不見」

這個 BOM 字元因為未顯示出來,在資料處理時經常會造成誤判。典型的災情:

line = f.readline().strip()
if line == "123中文字":      # ❌ 永遠不成立!
    ...

因為實際的值是'123中文字',比對字串、轉數字 (int())、當字典的鍵都會出錯,而且print 出來看起來完全正常,極難察覺。

解法一:用 UTF-8-sig 讀取

讀取有 BOM 的文件檔時,明確地加上encoding='UTF-8-sig',如此會將 BOM 與文件分離單獨處理。

[結果]

['123中文字\n', 'abcde\n', 'hello\n']
123中文

BOM 消失了,read(5)也正確讀到 5 個字元。

⭐ 讀檔一律用 UTF-8-sig 最安全

實測確認:UTF-8-sig讀「沒有 BOM」的檔案也完全正常

用 utf-8-sig 讀無 BOM 的檔 -> '123中文字\n'     ← 沒問題

也就是說,讀取時用UTF-8-sig有 BOM 會自動去掉、沒 BOM 也不會出錯,是最保險的選擇。
寫入時要用UTF-8(用UTF-8-sig會主動寫入 BOM,反而給別人添麻煩)。

動作建議編碼
讀取UTF-8-sig
寫入UTF-8

解法二:用編輯器去除 BOM

有經驗的程式設計師會用其他的文件編輯器,如 NotePad++,選擇「編碼 → UTF-8 碼」(而不是「UTF-8 碼(BOM 檔首)」)來去除 BOM。


相關主題與延伸閱讀