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

Python3 執行時出現 Non-UITF-8 code 錯誤

比較簡易直觀的解決方法有二:

  1. 使用附有轉碼功能的文字編輯器 (例如: UltraEdit...) 編輯舊文件, 然後 '另存新檔' 並指定使用 UTF-8 編碼.
  2. 繼續使用舊的編碼:
    • 必需在 .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), 我會再寫一篇貼文 (寫好了, 請連結這裡). 至於 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.

參考連結


arrow
arrow

    MagicJackTing 發表在 痞客邦 留言(0) 人氣()