Python2.7 中文字元編碼,使用Unicode時,選擇什麼編碼格式?

最近折騰了兩天Python的字元編碼,還是暈暈乎乎的

舉個例子,使用 Python的Unicode函數,把中文轉成對應的Unicode編碼

&>&>&> unicode("漢字", "gb2312")

u"u6c49u5b57"

但是如果把gb2312改成utf8, 系統就會報錯

&>&>&> unicode("漢字","utf8")
Traceback (most recent call last):
File "&

", line 1, in &
unicode("漢字","utf8")
File "C:Python27libencodingsutf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: "utf8" codec can"t decode byte 0xba in position 0: invalid start byte

我就不理解了,在 utf8和gb2312里,都可以表示「漢字」,為什麼使用utf8會報錯呢?

我後面又做了測試,如果在腳本中加上申明

# -*- coding: utf8 -*-

再執行

print unicode("漢字","utf8")

系統就不會報錯,會直接顯示 「漢字」

這裡Python編碼轉化的原理是什麼?

-------------------------------------------------------------------------------------------------------------------------------------

感謝@Kenneth 的回答,這個問題已經弄清楚了,還有一個問題(額,其實問題很多)

我使用pymssql模塊,連接到mssql資料庫,通過 select語句返回了一個值 "EA組"(我放在變數 unit 里), 我使用 print type(unit), 系統提示 是 &, 可以我列印的時候,顯示的 「EA×é」, 我懷疑是編碼轉換的問題,所以我分別加了 print unit.encode("gbk"), print unit.encode("utf-8"),但是結果是EA???? 和 EA×é, 這個是什麼原因?

網上有人說和資料庫字元有關係,我查了一下資料庫,查到一個和字元相關的屬性是Collation 為 Chinese_PRC_CI_AS(有關係嗎?)


關於編碼和亂碼的問題,我簡單講一下。

通常問這類問題的人是混淆了若干個不同的概念,並且他們自己也沒有意識到自己混淆了這些概念的。

  1. 終端顯示字元的編碼(windows下終端是cmd,linux下是各種terminal,遠程登錄是putty或者xshell)
  2. shell環境的編碼。比如中文版windows用的是gbk(向下兼容gb2312),大多數linux發行版使用的是utf-8(LANG=zh_CN.UTF-8)。
  3. 文本文件的編碼。這個通常取決於你的編輯器,而且有的編輯器支持多種編碼的話,你可以在文本開頭位置指定編輯器使用特定編碼。比如# -*- coding: utf8 -*-,vim看到這行會默認將這個腳本認定為utf-8兼容編碼格式。
  4. 應用程序的內部編碼。一個字元串,作為數據只是一個位元組數組,但是作為字元的數組,就有一個解析方式。java和python的內部字元編碼是utf-16,python和java都支持用不同的編碼來對位元組數組進行decode來得到字元數組。

拿題主的問題來解釋一下。

我在ubuntu kylin中文環境下默認terminal中做了同樣的實驗,但是結果和題主恰好相反:

看見沒有?

題主和我都沒有說謊,這是為什麼呢?

因為

unicode("漢字","gb2312")

這坨代碼的含義實際上是:將這裡顯示的這坨看上去像「漢字」的東西,用gb2312解碼,轉換為unicode字元串。unicode("漢字","utf-8")類似,只不過是用utf-8解碼,轉成unicode字元串。

(註:這裡涉及到兩個概念——unicode字符集和utf-8編碼——很多時候會用混淆,一個字符集表示一堆符號,而一種編碼是用二進位表示這個字符集的一種編碼方式。同樣是unicode字符集,可以有utf-8、utf-16、utf-32等等編碼方式。)

那這裡顯示的看上去像「漢字」的,tmd的到底是個什麼東西?

  1. 如果是在我的環境下,也就是linux utf-8環境下一個utf-8顯示終端,能顯示成「漢字」的這坨東西,它實際上是以utf-8編碼的「漢」字和「字」字兩個unicode字元。它們的真實字元值就是u"u6c49u5b57"(內碼),可以用"漢字".encode("hex")來查看當前終端下(utf-8編碼值)的十六進位碼。


    。所以我的命令是,將"e6b189e5ad97"這坨位元組數組,轉換為unicode的字元數組。——結果毫無難度,沒有錯誤,因為它本來就是utf-8編碼,所以能夠正常作為unicode字元解碼。

    但是unicode("漢字", "gb2312")就不一樣了,這個命令等同於「將"e6b189e5ad97"這坨東西,用gb2312編碼方式來解碼成字元」,但是實際上由於編碼空間並不兼容,使用gb2312編碼方式無法解碼這麼一坨奇葩的數據,所以葛屁了。
  2. 在題主的環境下,因為系統終端和默認文件編碼都是GBK,所以這個數實際上是

    這個實際上是gbk(兼容gb2312)的字元「漢字」的真實位元組數組。

    所以對這坨數據做unicode("漢字","utf8")會失敗——因為不管你怎麼想,雖然看上去是一樣,但是實際上不是同一坨東西啊!
  3. 題主現在弄了一個文件,在開始加上了

    # -*- coding: utf8 -*-

    這下編輯器看到了,知道這文件是utf-8的了。所以編輯器對讀入的一坨坨位元組用utf-8來解碼,對於輸出到磁碟的漢字也用utf-8來編碼。所以你在文件裡面看到的看上去像「漢字」的東西,就和第一種情況下想同了,當然代碼就跑得通。

    順便說一下,如果編輯器無視行首這行編碼聲明,或者編輯器無法支持utf-8格式,那麼你弄好的文件在那個編輯器下就會顯示亂碼,多麼簡單的道理啊。

所以,要能夠正常的顯示中文(或者其他什麼亂七八糟奇葩的多位元組文字),以下條件缺一不可:

  1. 終端和環境的編碼一致(本機通常是一致的,不一致常常出現在遠程登錄);如果不一致就需要有編輯器或者文本閱讀器做一個兼容兩者的轉換。
  2. 編輯器能夠認識文本編碼
  3. 系統擁有能顯示這種字元的字體。

這也就是我為什麼一直反對在程序文本中使用除ascii之外的所有編碼字元的原因。環境太複雜了,繞開問題遠比解決問題輕鬆。

這下你明白了?


我覺得關鍵是區分「位元組」和「字元」的概念,還要知道一點點字體的常識。

「字元」可以看成是一個抽象概念,如當樓主說「漢字」,其實他意思是表達的是表示這麼一個概念的兩個字元。

當字元在計算機中表示的時候,需要編碼成二進位(位元組),於是就出現了不同的編碼方式,如 GBK, UTF-8 等。如 Kenneth 展示的,「漢字」這兩個字元在 GBK 中編碼為 0xBABAD7D6,而在 UTF-8 中編碼為 0xE6B189E5AD97。

最終顯示時,則還要根據所使用的字體,把抽象的字元轉化成具象的圖像。

所以,樓主的第一個問題在於雖然你看到的是「漢字」的圖像,但其在該腳本的源文件中的位元組編碼可能是任何一種——在 Windows 下是 GBK 或 GB18030 等。於是 python 看到的是一串 GBK / GB18030 編碼的位元組,而你試圖告訴 python 這是 UTF-8 編碼的,那自然報錯了。

第二個問題,對 SQL Server 不熟,不過看起來原因是當你把從資料庫讀出的數據(位元組形式,可能是 GBK 等非 Unicode 編碼)放入 unit 這個變數的時候,程序錯把非 Unicode 編碼的位元組當成 Unicode 編碼解釋了。那麼排查思路應該是搞清楚數據在讀出時是什麼編碼(這可能跟數據存入時的編碼相關,也可能跟資料庫配置有關),以及存入 unit 時程序做了哪些轉換。


起初,因為計算機只考慮英文字元,總共127,8個比特編碼一個字元即夠用,這就是ASCII編碼。先活下來再說,走在前面。

後來,為了拓展日本市場,中國市場,非洲市場,語言環境複雜多變,8個比特明顯不夠用了。這時,中文採用gb2312編碼,日語採用XXX編碼,泰語採用OOO編碼。群雄並起的年代,荒煙蔓草。

於是,Unicode編碼應運而生,一般採用16個比特2個位元組來統一編碼。完成大一統,登上光明頂。

但是,即使漢語環境,也可能只發送純英文字元,Unicode編碼浪費空間,於是升級為可變長編碼UTF-8。解決內部矛盾,不服的統統站出來。


這個問題可以參考我的另一個回答,

python 中文亂碼出現,不知道是什麼原因? - 小頭的回答

在Python2中,str類型數據的編碼格式取決於具體環境,在cmd輸入」漢字」時,實際上是以cp936編碼的,因此這裡使用 unicode("漢字", "gb2312")把它轉換成unicode類型(gbk兼容gb2312),而不是 utf-8

而這種情況在文件中又不同(沒錯,python2就是這麼蛋疼),文件中str類型數據的編碼格式取決於文件的實際編碼(不是文件開頭的聲明哦),當文件使用utf-8編碼時,str的編碼也是utf-8,此時在文件中使用:

print unicode("漢字","utf8")

實際python做了兩個操作

  1. 將str類型數據 "漢字" 以 utf-8格式轉換(decode)成 unicode類型

  2. 將unicode類型數據 」漢字「 輸出

這種情況不會報錯且輸出不亂碼,原因是

  • 這裡的str類型與文件一樣採用utf-8編碼,如果在文件添加 print repr("漢字") ,可以看到結果是"xe4xbdxa0xe5xa5xbd",而不是上面截圖的""\xba\xba\xd7\xd6""

  • 列印unicode類型時,print自動調用gbk編碼輸出
  • 如果這裡文件使用gbk編碼,內容不變,同樣會報與終端同樣的錯

資料庫那個問題,建議先確認mysql是否正確配置了utf-8編碼


@科研君 可以參考一下他的關於「對於位元組編碼,程序員的話應該了解它的哪些方面?」回答。我覺得比較詳細。


推薦閱讀:

用python模擬登錄知乎,爬回來的是亂碼?
在計算機中為何不直接使用UTF8編碼進行存儲,而要使用Unicode再轉換成UTF8?
Unicode 是不是只有兩個位元組,為什麼能表示超過 65536 個字元?
為什麼編碼(GBK、Big-5 等)問題這種歷史遺留始終得不到解決?
如何用 Potplayer 消除 .srt 文件的中文字幕亂碼?

TAG:Unicode統一碼 | 編碼 | Python入門 |