Python 編碼問題

做爲中文用戶,初學 Python 最容易碰到的問題估計就是編碼問題了。明明英文的均可以用到中文的時候就要出問題,並且出錯信息難以理解,想要解決問題又不知道從何開始。幸運的是編碼問題經過預防性的措施是很好避免的。下面從幾個方面來說講 Python 中處理中文及 Unicode 容易碰到的問題。python

Unicode 編碼基礎

這裏很是簡單的講一下編碼知識,此部分表述可能不太準確,若是你對 Unicode 更爲了解的話請聯繫我幫忙糾正程序員

你能夠想象 Unicode 是一個很大的表,裏面有着世界上全部的文字的個體,如英文中的字母,中文的漢字。事實上 Unicode 標準中每個字都有一個惟一對應的編號,比如說 '中'字 對應十六進制 0x4E2D,而字母 'a' 對應的是十六進制 0x0061。這個編號是由 Unicode Consortium 這個組織來肯定的。 若是說用這個編碼來對應字符來用於表示字符,理論上是能夠的,這樣的話就是每個數字編號能對應一個字符。django

而實際狀況中,不是每篇文章都用獲得世界上全部的字符。譬如一篇英文文章就只有英文字母加上一些符號,用 Unicode 來進行存儲的話每一個字符要浪費太多的空間。因此就有各類類型的編碼產生。編碼咱們這裏能夠理解就是將一部分的 Unicode (好比說全部的中文,或者全部的日文)字符,以某種方式肯定另一個符號來表明他。中文經常使用編碼有 UTF8 和 GBK,仍然以 '中'字 爲例, UTF8 編碼將對應 '中'字 的 Unicode 編號 0x4E2D 拆成三個的編號的組合,[0xE4, 0xB8, 0xAD],只有這幾個連在一塊兒的時候纔會被做爲一個 '中'字 顯示出來;做爲對比,GBK 編碼將 '中'字 對應的 Unicode 編號 0x4E2D 編碼成爲兩個編號的組合 [0xD6, 0xD0],在 GBK 編碼環境下只有這兩個編號一塊兒時,纔會顯示爲 '中'字。windows

上面的例子中,若是把 UTF8 編碼後的 [0xE4, 0xB8, 0xAD] 放到 GBK 環境下來顯示會怎樣?這幾個編號跟 '中'字 在 GBK 下的編碼 [0xD6, 0xD0],不一樣,則顯然不會顯示爲 '中'字。這三個字符會跟排在其先後的字符一塊兒,按照 GBK 的編碼規則找有沒有對應的字符。結果有可能顯示出一個絕不相關的字符,有時候爲符號或者乾脆不顯示,這種狀況就算產生了亂碼編輯器

Python 2.x 中的 String 與 Unicode

在 Python 2.x 中是有兩種字串符相關類型的,分別爲 String 和 Unicode,二者提供的接口很是相似,有時候又能自動轉換,蠻容易誤導人的。在 Python 3 中 這兩個類型分別用 Bytes 和 String 替代了。這個名字更能說明二者的本質:Python 2.x 中的 String 中存儲的是沒有編碼信息的字節序列,也就是說 String 中存儲的是已經編碼事後的序列,但他並不知道自身是用的哪一種編碼。相反的 Unicode 中存儲的是記載了編碼的字串信息,其中存儲的就是相應字符的 Unicode 編號。在這裏用程序來講明,咱們創建一個簡單的腳本名字爲 encoding.py,代碼以下:ide

#!/usr/bin/python
# -*- coding: utf-8 -*-

strs = "這是中文"
unis = "這也是中文".decode("utf8")

print strs[0:2]
print unis[0:2].encode('gbk')

print len(strs)
print len(unis)

前面兩行後面會解釋到,就是限定運行環境以及該腳本文件的編碼格式。此腳本在這裏能夠下載,若是你要本身寫的話請務必確保腳本的編碼是 utf8 而不是別的。在 Windows 下的運行結果在這裏,我以爲正好能說明問題:google

C:\SHARED\Dev\scripts>encoding.py
榪
這也
12
5

這裏須要說明,咱們的程序是 UTF8 編碼,主要意義是該程序中的全部直接寫出來的字串符(用"", ''括起來的字串符)是運用 UTF8 格式編碼的;然而 Windows 下的命令行是 GBK 環境。這裏 strs 是一個 String。事實上在 Python 2.x 中直接寫在程序中的字串符,其類型都是 String(這裏不考慮 string literal)。咱們先直接輸出 strs[0:2],獲得的是一個亂碼字符(這個字符只是碰巧湊成是一個字)。如上面說的,String 中存儲的是沒有編碼信息的字串序列,這裏就是將strs中前兩個編號取出並嘗試顯示。因爲命令行環境爲 GBK 編碼,這裏對應的字碰巧湊成了一個字,可是跟本來的字沒有任何關係。編碼

unis 是由一個 String 調用 decode() 方法獲得,這正是在 Python 2.x 中取得 Unicode 的最基本的方式。因爲 String 並不知道它自己是由什麼編碼格式來進行的編碼,這裏是咱們的責任來肯定他原來是用哪一種編碼方式進行編碼。咱們知道代碼中的編碼格式是 UTF8,因此咱們能夠用調用 String 的decode() 方法來進行反編碼,也就是解碼, 把字串符從某種編碼後的格式轉換爲其惟一對應的 Unicode 編號。unis 爲解碼得到的結果,其在 Python 2.x 中對應類型就是 Unicode,其中存儲的就是 每一個字符對應的 Unicode 編號。url

咱們嘗試輸出 unis 的前兩個字符,在這裏咱們調用了 Unicode 的 encode() 方法。這就是編碼的過程。咱們知道 Windows 命令行下的編碼是 GBK,只有採用 GBK 編碼的字符才能正確顯示。因此在這裏咱們經過調用 Unicode 的 encode() 方法,將 unis 中存儲的 Unicode 編號 按照 GBK 的規則來進行編碼,並輸出到屏幕上。這裏咱們看到這裏正確的顯示了 unis 中的前兩個字符。要注意的是在命令行中直接 print Unicode 的話 Python 會自動根據當前環境進行編碼後再顯示,但這樣掩蓋了二者的區別。建議老是手動調用 encode 和 decode 方法,這樣本身也會清楚一些。spa

後面二者長度的差異也是佐證咱們以前的例子。strs 中存儲的是 UTF8 編碼後的編號序列,上面看到一箇中文字符在 UTF8 編碼後變成三個連續的,因此 strs 長度爲 3x4 = 12。你能夠想象 strs 中存放的並非中文,而是一系列沒有意義的比特序列;而 unis 中存儲的是對應的中文的 Unicode 編碼。咱們知道每個字符對應一個編號,因此五個字對應五個編號,長度爲 5。

避免,和解決編碼產生的問題

瞭解了 Python Unicode 編碼解碼的這些概念後,咱們來看看如何儘可能的避免遇到讓人煩心的編碼問題。

首先若是你的代碼中有中文,那麼必定要務必聲明代碼的編碼格式。根據 PEP-0263 中的介紹,在程序的最開始加上如下兩行註釋就能肯定編碼:

#!/usr/bin/python
# -*- coding: utf-8 -*-

其中 utf-8 就是指定的編碼格式。事實上你應該老是使用 UTF8 做爲你 Python 程序的編碼格式,由於將來的 Python 3 全部文件都將默認以 UTF8 編碼。另外除了聲明,你必須肯定你用來編輯 Python 程序的編輯器是否是真的以 UTF8 編碼來存儲文件。

以後就是養成關於編碼解碼的好習慣。當你的程序有 String 做爲輸入時,應該儘早的將其轉換爲Unicode,再在程序中進行處理。再輸出的時候,也要儘量玩,直到最後輸出的時刻纔將 Unicode 編碼爲所需編碼格式的 String 進行輸出。一樣的你必須保持你程序內部全部參與運算的字串都是Unicode 格式。不少著名的 Python 庫例如 django 就是採用的這種方式,效果也蠻好。千萬不要依賴 Python 本身進行二者之間的轉換,也不要將 String 和 Unicode 放在一塊兒運算,這些行爲一方面十分容易引發錯誤,另外一方面在 Python 3 中已經沒法再現。

雖然說肯定 String 的編碼格式是程序員的責任,但有時候你真的不知道有些字串符究竟是什麼編碼的。這裏有一個神奇 chardet 可以幫助你。如下是摘自其頁面上的例子,很好了說明了它的做用:讀入任意一串字符,猜想其編碼格式,而且給出猜想的確信度。

>>> import urllib
>>> urlread = lambda url: urllib.urlopen(url).read()
>>> import chardet
>>> chardet.detect(urlread("http://google.cn/"))
{'encoding': 'GB2312', 'confidence': 0.99}

>>> chardet.detect(urlread("http://yahoo.co.jp/"))
{'encoding': 'EUC-JP', 'confidence': 0.99}

>>> chardet.detect(urlread("http://amazon.co.jp/"))
{'encoding': 'SHIFT_JIS', 'confidence': 1}

>>> chardet.detect(urlread("http://pravda.ru/"))
{'encoding': 'windows-1251', 'confidence': 0.9355}

若是 confidence 很是低的話或者 chardet 直接報錯,多半是字串通過屢次錯誤編碼解碼,要從別的地方找辦法解決問題。

在處理包含漢字的文本文件時,一個很常見的問題就是有時候會碰到帶有 UTF BOM 的文件。這個簡單講就是文件頭幾個字節是用來表示文件是大端仍是小端表示。在實際中用的不多,並且會帶來很頭疼的問題。有時候你肯定你有一個文件是 UTF8 編碼的,但讀進來頭幾個字節就出錯,那麼十有八九就是這個的問題。Python 在讀取文件時仍然是全部字節順序讀進來,不會透明的處理這個東西。因此要麼你能夠用編輯器來把文件另存爲無 BOM 的,要麼在 Python 中作處理。在標準庫中有 codec 裏面提供了相關功能:

import codecs
s = f.read(3)
if s == codecs.BOM_UTF8:
    print "BOM detected"

這樣能夠簡單檢測 BOM 是否存在,剩下的部分就要你本身發揮了。

若是上面的介紹還不能讓你理解 Unicode 的概念,這裏還有幾篇關於這個問題的文章:

相關文章
相關標籤/搜索