Python 編碼轉(zhuǎn)換與中文處理

python 中的 unicode是讓人很困惑谅摄、比較難以理解的問題. 這篇文章 寫的比較好秘症,utf-8是 unicode的一種實現(xiàn)方式站削,unicode污桦、gbk植影、gb2312是編碼字符集.

py文件中的編碼

Python 默認腳本文件都是 ANSCII 編碼的研铆,當文件 中有非 ANSCII 編碼范圍內(nèi)的字符的時候就要使用"編碼指示"來修正一個 module 的定義中拌汇,如果.py文件中包含中文字符(嚴格的說是含有非anscii字符)瞧预,則需要在第一行或第二行指定編碼聲明:# -*- coding=utf-8 -*- 或者 #coding=utf-8
其他的編碼如:gbk艺谆、gb2312也可以榨惰;否則會出現(xiàn):

SyntaxError: Non-ASCII character '\xe4' in file test.py on line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

python中的編碼與解碼

先說一下python中的字符串類型,在python中有兩種字符串類型静汤,分別是 strunicode琅催,他們都是basestring的派生類居凶;

  • str類型是一個包含Characters represent (at least) 8-bit bytes的序列;
  • unicode 的每個 unit 是一個 unicode obj;

在str的文檔中有這樣的一句話:

The string data type is also used to represent arrays of bytes, e.g., to hold data read from a file.

也就是說在讀取一個文件的內(nèi)容恢暖,或者從網(wǎng)絡上讀取到內(nèi)容時排监,保持的對象為str類型;如果想把一個str轉(zhuǎn)換成特定編碼類型杰捂,需要把str轉(zhuǎn)為Unicode,然后從unicode轉(zhuǎn)為特定的編碼類型如:utf-8舆床、gb2312等。

python 編碼轉(zhuǎn)換函數(shù)

unicode 轉(zhuǎn)為 gb2312,utf-8等,使用 encode(encoding)

# -*- coding=UTF-8 -*-
if __name__ == '__main__':
    s = u'中國'
    s_gb = s.encode('gb2312')

utf-8,GBK轉(zhuǎn)換為 unicode 使用 unicode(s,encoding) 或者 s.decode(encoding)

#coding=UTF-8
if __name__ == '__main__':
    s = u'中國'
    #s為unicode先轉(zhuǎn)為utf-8
    s_utf8 =  s.encode('UTF-8')
    assert(s_utf8.decode('utf-8') == s)

普通的 str 轉(zhuǎn)為 unicode,

#coding=UTF-8
if __name__ == '__main__':
    s = '中國'
    su = u'中國''
    #s為unicode先轉(zhuǎn)為utf-8
    #因為s為所在的.py(# -*- coding=UTF-8 -*-)編碼為utf-8
    s_unicode =  s.decode('UTF-8')
    assert(s_unicode == su)
    #s轉(zhuǎn)為gb2312:先轉(zhuǎn)為unicode再轉(zhuǎn)為gb2312
    s.decode('utf-8').encode('gb2312')

如果直接執(zhí)行s.encode('gb2312')會發(fā)生什么嫁佳?

#coding=UTF-8
if __name__ == '__main__':
    s = '中國'
    s.encode('gb2312')

這里會發(fā)生一個異常:Python 會自動的先將 s 解碼為 unicode 挨队,然后再編碼成 gb2312。因為解碼是python自動進行的蒿往,我們沒有指明解碼方式盛垦,python 就會使用 sys.defaultencoding 指明的方式來解碼。很多情況下 sys.defaultencoding 是 ANSCII瓤漏,如果 s 不是這個類型就會出錯腾夯。
拿上面的情況來說,我的 sys.defaultencoding 是 anscii蔬充,而 s 的編碼方式和文件的編碼方式一致蝶俱,是 utf8 的,所以出錯了:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

對于這種情況饥漫,我們有兩種方法來改正錯誤:

  • 明確的指示出 s 原來的編碼方式

!/usr/bin/env python

-- coding: utf-8 --

s = '中文'
s.decode('utf-8').encode('gb2312') ```

  • 更改 sys.defaultencoding 為文件的編碼方式

! /usr/bin/env python

-- coding: utf-8 --

import sys
reload(sys) # Python2.5 初始化后會刪除 sys.setdefaultencoding 這個方法榨呆,我們需要重新載入
sys.setdefaultencoding('utf-8')
str = '中文'
str.encode('gb2312')


### 文件編碼與print函數(shù)
建立一個文件test.txt,文件格式用ANSI庸队,內(nèi)容為:"abc中文",用python來讀取

coding=gbk

print open("Test.txt").read()

結(jié)果:abc中文

把文件格式改成UTF-8:
結(jié)果:abc涓 枃

顯然积蜻,這里需要解碼:

coding=gbk

import codecs
print open("Test.txt").read().decode("utf-8")

結(jié)果:abc中文

上面的test.txt我是用Editplus來編輯的,但當我用Windows自帶的記事本編輯并存成UTF-8格式時彻消,運行時報錯:

Traceback (most recent call last):
File "ChineseTest.py", line 3, in <module>
print open("Test.txt").read().decode("utf-8")
UnicodeEncodeError: 'gbk' codec can't encode character u'\ufeff' in position 0: illegal multibyte sequence

原來竿拆,某些軟件,如notepad宾尚,在保存一個以UTF-8編碼的文件時丙笋,會在文件開始的地方插入三個不可見的字符(***0xEF 0xBB 0xBF,即BOM***)央勒。因此我們在讀取時需要自己去掉這些字符,python中的codecs module定義了這個常量:

coding=gbk

import codecs
data = open("Test.txt").read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
print data.decode("utf-8")

結(jié)果:abc中文

### 一點遺留問題
在第二部分中澳化,我們用unicode函數(shù)和decode方法把str轉(zhuǎn)換成unicode崔步。為什么這兩個函數(shù)的參數(shù)用"gbk"呢?第一反應是我們的編碼聲明里用了gbk(# coding=gbk)缎谷,但真是這樣井濒?修改一下源文件:

coding=utf-8

s = "中文"
print unicode(s, "utf-8")

運行灶似,報錯:

Traceback (most recent call last):
File "ChineseTest.py", line 3, in <module>
s = unicode(s, "utf-8")
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 0-1: invalid data

顯然,如果前面正常是因為兩邊都使用了gbk瑞你,那么這里我保持了兩邊utf-8一致酪惭,也應該正常,不至于報錯者甲。
更進一步的例子春感,如果我們這里轉(zhuǎn)換仍然用gbk:

coding=utf-8

s = "中文"
print unicode(s, "gbk")

結(jié)果:中文

翻閱了一篇英文資料,它大致講解了python中的print原理:
>When Python executes a print statement, it simply passes the output to the operating system (using fwrite() or something like it), and some other program is responsible for actually displaying that output on the screen. For example, on Windows, it might be the Windows console subsystem that displays the result. Or if you're using Windows and running Python on a Unix box somewhere else, your Windows SSH client is actually responsible for displaying the data. If you are running Python in an xterm on Unix, then xterm and your X server handle the display.To print data reliably, you must know the encoding that this display program expects.

簡單地說虏缸,**python中的print直接把字符串傳遞給操作系統(tǒng)鲫懒,所以你需要把str解碼成與操作系統(tǒng)一致的格式**。Windows使用CP936(幾乎與gbk相同)刽辙,所以這里可以使用gbk窥岩。最后測試:

coding=utf-8

s = "中文"
print unicode(s, "cp936")

結(jié)果:中文

### python 編碼 檢測
使用 `chardet` 可以很方便的實現(xiàn)字符串/文件的編碼檢測,例子如下:

import urllib
rawdata = urllib.urlopen('http://www.google.cn/').read()
import chardet
chardet.detect(rawdata)
{'confidence': 0.98999999999999999, 'encoding': 'GB2312'}

在工作中,經(jīng)常遇到宰缤,讀取一個文件颂翼,或者是從網(wǎng)頁獲取一個問題,明明看著是gb2312的編碼慨灭,可是當使用decode轉(zhuǎn)時朦乏,總是出錯,這個時候缘挑,可以使用decode('gb18030')這個字符集來解決集歇,如果還是有問題,這個時候语淘,一定要注意诲宇,decode還有一個參數(shù),比如惶翻,若要將某個 String對象s從gbk內(nèi)碼轉(zhuǎn)換為UTF-8姑蓝,可以如下操作 
`s.decode('gbk').encode('utf-8′) `
可是,在實際開發(fā)中吕粗,我發(fā)現(xiàn)纺荧,這種辦法經(jīng)常會出現(xiàn)異常: 
>UnicodeDecodeError: ‘gbk' codec can't decode bytes in position 30664-30665: illegal multibyte sequence

這是因為遇到了非法字符——尤其是在某些用C/C++編寫的程序中,***全角空格***往往有多種不同的實現(xiàn)方式颅筋,比如`\xa3\xa0`宙暇,或者`\xa4\x57`,這些字符议泵,看起來都是全角空格占贫,但它們并不是“合法”的全角空格(真正的全角空格是`\xa1\xa1`),因此在轉(zhuǎn)碼的過程中出現(xiàn)了異常先口。這樣的問題很讓人頭疼型奥,因為只要字符串中出現(xiàn)了一個非法字符瞳收,整個字符串——有時候,就是整篇文章——就都無法轉(zhuǎn)碼厢汹。 解決辦法: 
`s.decode('gbk', ‘ignore').encode('utf-8′) `
因為decode的函數(shù)原型是 `decode([encoding], [errors='strict'])`螟深,可以用第二個參數(shù)控制錯誤處理的策略,默認的參數(shù)就是strict烫葬,代表遇到非法字符時拋出異常界弧; 

* 如果設置為ignore,則會忽略非法字符厘灼; 
* 如果設置為replace夹纫,則會用?取代非法字符; 
* 如果設置為xmlcharrefreplace设凹,則使用XML的字符引用舰讹。 

參考:

* http://blog.chinaunix.net/u2/68206/showart.php?id=668359
* http://www.pythonclub.org/python-basic/codec
* http://www.pythonclub.org/python-scripts/quanjiao-banjiao
* http://www.pythonclub.org/python-basic/chardet
Edit By [MaHua](http://mahua.jser.me)
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闪朱,隨后出現(xiàn)的幾起案子月匣,更是在濱河造成了極大的恐慌,老刑警劉巖奋姿,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锄开,死亡現(xiàn)場離奇詭異,居然都是意外死亡称诗,警方通過查閱死者的電腦和手機萍悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寓免,“玉大人癣诱,你說我怎么就攤上這事⊥嘞悖” “怎么了撕予?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜈首。 經(jīng)常有香客問我实抡,道長,這世上最難降的妖魔是什么欢策? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任吆寨,我火速辦了婚禮,結(jié)果婚禮上踩寇,老公的妹妹穿的比我還像新娘啄清。我一直安慰自己,他們只是感情好姑荷,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布盒延。 她就那樣靜靜地躺著,像睡著了一般鼠冕。 火紅的嫁衣襯著肌膚如雪添寺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天懈费,我揣著相機與錄音计露,去河邊找鬼。 笑死憎乙,一個胖子當著我的面吹牛票罐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泞边,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼该押,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阵谚?” 一聲冷哼從身側(cè)響起蚕礼,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梢什,沒想到半個月后奠蹬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嗡午,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年囤躁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荔睹。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡狸演,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出应媚,到底是詐尸還是另有隱情严沥,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布中姜,位于F島的核電站消玄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丢胚。R本人自食惡果不足惜翩瓜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望携龟。 院中可真熱鬧兔跌,春花似錦、人聲如沸峡蟋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仅乓,卻和暖如春赖舟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夸楣。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工宾抓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豫喧。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓石洗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親紧显。 傳聞我的和親對象是個殘疾皇子讲衫,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容