Python3
Python3 已經支援 unicode, 所有的 .py 檔, 預設都是 UTF-8 編碼, 有沒有附帶 unicode BOM (Byte Order Mark) 都可以. 所以只要使用預設是 UTF-8 的編輯器 (例如: VS Code 或者在 Win10 下使用 notepad) 建立新的 .py 檔即可.
但是直接用 Python3 執行非 UTF-8 編碼的 (例如: Big5, UTF-16 BOM, UTF-16 no BOM 等編碼) .py 檔, 就會出錯 (Non-UITF-8 code). 例如使用 Python3 執行 Big5 編碼的這一段程式:
text = '字串測試'
print(len(text))
就會直接報錯, 如下圖:
![Non-UTF-8 code Non-UTF-8 code](https://imageproxy.pixnet.cc/imgproxy?url=https://pic.pimg.tw/magicjackting/1632283844-656858291-g_l.png)
Python3 執行時出現 Non-UITF-8 code 錯誤
比較簡易直觀的解決方法有二:
- 使用附有轉碼功能的文字編輯器 (例如: UltraEdit...) 編輯舊文件, 然後 '另存新檔' 並指定使用 UTF-8 編碼.
- 繼續使用舊的編碼:
- 必需在 .py 檔的第一行加上一行編碼宣告 (檔案內的編碼宣告和檔案本身的編碼務必要一致, 不然一樣是報錯).
這個編碼宣告(註一)的規定其實非常的寬鬆: 只要符合 coding[=:]\s*([\w-_.]+)(註二) 這個正規表示式即可, 沒有硬性規定. 所以你可能看過許多種寫法, 下面這些都是合法的宣告:# -*- coding: latin-1 -*-
# vim:fileencoding=utf-8
# coding=Big5
- 如果 .py 檔第一行已經被 shebang (#!) 佔據, 請改放在第二行. 如下:
#!/usr/bin/env python3 # coding=Big5 text = '字串測試' print(len(text)) # 顯示 4
註一: 這個編碼宣告自 Python v2.3 版開始支援, 一直都沒有更動過. 所以一般含有非 ASCII 編碼的 python2 .py 檔 (例如: 中文) 應該都已經有這一行.
註二: 這串正規表示式的意思是: coding 接著 (=或者:二選一) 再接著任意個空白 (可以沒有) 再接著的指定的編碼名稱. 而編碼名稱是由一個以上的下列字元組成: 一般字元 (包含大小寫英文, 數字, 底線), -, _, ..
.py 檔多的時候二個轉換方法都有不小的工作量.
同時還有一個字串長度和原本 Python2 不一樣的副作用. 這是因為 Python3 在執行時, 字串實例內部使用的都是 unicode. 所以上例中即便 .py 檔指定使用 Big5 編碼, Python3 依舊會自動將程式中的字面字串轉成 unicode 編碼. 所以最後是顯示 4 (也就是 4 個字元).
同時另人驚喜的是: 這段程式無論是改 coding (Big5, utf-8, utf-16, utf-16-le, ...), 還是字面字串有沒有加上前綴字u, 使用 Python3 執行答案都是 4, 而不是像 Python2 那樣可能是 4, 8, 或者 12.
同時, 基於字串內部資料使用的都是 unicode 的原因, 實作時可能需要將字串轉為指定的編碼. 我們可以使用字串內建的 encode() 方法將資料轉換成一個 bytes 實例. 相反的, 可以使用 decode() 方法將一個 bytes 實例, 轉換回字串實例.
>>> '中文'.encode('big5')
b'\xa4\xa4\xa4\xe5'
>>> b'\xa4\xa4\xa4\xe5'.decode('big5')
'中文'
>>> b'\xa4\xa4\xa4\xe5'.decode('utf-8')
Traceback (most recent call last):
File "", line 1, in
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa4 in position 0: invalid start byte
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
>>>
Python3 字符編碼重點:
- 字串 (str) 實例內部的字符一律是 unicode 編碼.
- 字串可以用 encode() 方法轉成不同編碼的 bytes (轉換一般不會出錯, 除非有 unicode 特殊區段的字符)
- bytes 並未含有編碼資訊.
- bytes 可以選用不同的編碼方式 decode() 成 unicode 編碼的字串. 選錯了編碼就可能會解釋錯誤 (亂碼?), 甚至程式直接報錯 (不存在對應字符).
- 不同編碼的 bytes 無法直接互轉. 只能轉成字串再轉成其他編碼的 bytes. 所以, 各種編碼表之間的關係是以 unicode 為中心的星狀拓樸, 而不是一個各種編碼之間任意連結的網狀拓樸.
同樣的, 檔案的讀/寫可能也需要指定編碼. Python3 預設已經將 open() 函數, 指定為 encoding='UTF-8'(註三). 下例是指定以 Big5 編碼開啟並讀取檔案:
name = input('請輸入檔名:')
with open(name, 'r', encoding='Big5') as file:
content = file.read()
print(content)
注意事項:
- 以文字模式讀取 (mode='r') 的資料內容 (上例的變數 content, 資料型態為字串) 已經轉成 unicode 編碼. 同樣的以文字模式寫出 (mode='w') 字串時 (file.write()) 字串內容同樣也是 unicode 編碼, 檔案內容才是指定的編碼.
- 如果改用二進位模式 (binary mode) (mode='rb' 或 mode='wb'), 則讀寫的資料內容不會發生轉碼.
註三: 相對於 Python3 在 Windows 以外的平台上 open() 函數都將 encoding 參數預設為 'utf-8', 但 python for windows (Python3) 在 Win10 正體中文環境下, 預設的開檔 encoding 依然是 CP950 (即 Big5 編碼). 想要改變這個預設值, 我們可以在環境變數中新增一個環境變數設定 PYTHONUTF8=1, 或者執行 .py 檔時加上參數 -Xutf8 即可以和 Linux 及 Mac OSX 一樣, 讓 open() 函數預設使用 encoding='utf-8' 參數.
Python2
相對於 Python3 直接使用 UTF-8 為 .py 檔預設編碼, 執行時字串資料型態內部字符也使用 unicode 編碼. Python2 的 .py 檔一開始只支援 ASCII 編碼.
後來為了支援 unicode, Python v2.2 開始提供在一般字面字串 (String Literal) 前多加一個前綴字 u 用來產生 unicode 物件.
接著 Python v2.3(註四) 為了解決偵測 .py 檔編碼的問題, 開始支援在 .py 檔的最前面加上一行編碼宣告, 用以宣告程式的編碼.
註四: 你沒有看錯, 程式中 u'abcd' 的寫法從 Python v2.2 開始支援. 但是宣告程式編碼的寫法是從 Python 2.3 開始支援.
下面這個範例程式會顯示的是 8 而不是 4. 這是因為 Big5 編碼的每一個中文字佔了 2 個位元組, 同時字串 (str) 資料型態在 Python2 還不是 unicode 物件 (它其實和 Python3 的 bytes 差不多). 所以 4 個 Big5 中文一共佔了 8 個位元組.
# coding=Big5
text = '字串測試'
print len(text) # 顯示 8
OK. 那如果我們把程式改成用 UTF-8 來編碼呢? 它應該要顯示多少?
# coding=utf-8
text = '字串測試'
print len(text) # 顯示 ??
我們套用上面的邏輯, 絕大部份的 UTF-8 編碼中文字符都是一個字 3 個 bytes, 所以它應該是顯示 12.
如果把原本的範例程式, 稍作修改, 字面字串加上一個前綴字 u:
# coding=Big5
text = u'字串測試'
print type(text) # 顯示 <type 'unicode'>
print len(text) # 顯示 4
對 Python2 解譯器 (interpreter) 來說, 看到第一行知道接下來的程式中字面字串 (String Literal) 都要使用 Big5 編碼. 再看到 '字串測試' 的前綴字 u 時, 其對應的動作是將原先的 Big5 編碼的字面字串轉換成 unicode 物件.
而 Python2 的 unicode 物件操作方法和 Pythpn3 的字串 (str) 是一樣的 (應該說 Python3 的字串 (str) 資料型態是繼承自 Python2 的 unicode 物件). 所以對 Python2 而言是 unicode 物件可以 encode(), 而一般字串則可以 decode().
相對的 Python2 的檔案讀寫則單純多了: 進出都是資料原始模樣, 要轉碼自己來. 但是實務上, 確是有層層障礙.
with open('text.txt', 'r') as file:
mystr = file.read() # mystr 是 str 物件
print mystr
上面這一段程式只是簡單的以文字模式讀取檔案, 並輸出到螢幕. 在正體中文 Windows 環境下, big5 編碼的中英文混合文字檔都 OK. 可以正常執行. 但是如果換成 unicode 文字檔就掛了.
with open('text.txt', 'r') as file:
mystr = file.read() # mystr 是 str 物件
# print mystr # 換成 UTF-8 no BOM 文字檔這一行會出錯
print mystr.decode('utf-8') # 自行判斷標準輸出編碼
print mystr.decode('utf-8').encode('big5') # 標準輸出編碼為 big5
你必需改用上例中的第 4 行或者第 5 行, 才能正確輸出. 但事實上, 它需要更多的配套:
- 文字檔內容必需是 UTF-8 no BOM 編碼. 多了 BOM 程式就掛了.
- 必需在正體中文 Windows 的命令提示字元 (也就是 cmd.exe) 下執行
- 命令提示字元必需使用正體中文編碼 (chcp 950), 其他編碼都不行. 例如: chcp 65001 (UTF-8).
關於使用 Python3 時如何正確處理 unicode 資料檔 (不管有沒有 BOM), 我會再寫一篇貼文 (寫好了, 連結在這裡 " Python: 關於 Unicode 的 BOM"). 至於 Python2 奉勸各位就算了吧 (都快 2021 年底了, python2 都已經在 2020/01/01 正式停更了).
Unicode Code Point 相關函數
原本 Python2 的字串有二個 ASCII 編碼相關函數:
- chr(): 用來將數字轉換為對應 ASCII 編碼的字符. 回傳的 ASCII 字符是一個字串.
- ord(): 的作用則剛好相反, 將字串轉成對應的 ASCII 編碼值.
Python2 支援 unicode 之後, 將為了不破壞原本 chr() 函數的回傳值界面, 所以多提供了一個支援 unicode code point 的函數 unichr(), 回傳值是一個 unicode 物件, 而不是原本的字串. 而 ord() 則沒有回傳值界面的問題, 所以就直接擴充了對 unicode code point 的支援. 也因此不存在你想像的 uniord() 函數.
>>> chr(65)
'A'
>>> unichr(65)
u'A'
>>> unichr(28204)
u'\u6e2c'
>>> ord('A')
65
>>> ord(u'A')
65
>>> ord(u'測')
28204
>>>
Python3 由於已經將 Python2 的 unicode 物件直接變成字串, 所以又回復到最開始 Python2 的狀態: 只有 chr() 和 ord(), 但它們都支援 unicode.
參考連結
- Windows 的 Code Page 資訊: https://en.wikipedia.org/wiki/Windows_code_page
- 我的貼文: "Python: 關於 unicode 的 BOM"