標籤:

初探python編碼

背景:在實際數據處理中,我們或多或少會接觸到中文,如兩個dc pack包的diff。使用python對中文數據 處理難免會遇到編碼問題。

python裡面主要考慮三種編碼:

1、源文件編碼:

如果我們在源文件中使用中文注釋或中文docstring或中文字元串,如不明確指定應使用哪個中文字符集,解 釋器將無法處理我們的程序。這是因為解釋器默認程序使用的是ASCII或ISO-8859-1(即LATIN-1)編碼。

解決方法是在文件頭部使用coding聲明(往往緊跟在#!注釋行後面):

#coding: gbk或# coding=gbk或# -*- coding: gbk -*-

2、內部編碼: python內部表示一個字元串有兩種方式:一種是普通的str對象(基於位元組的字元表示),另一種是unicode字元 串。它們之間可相互轉換:

unicode轉其他編碼形式的str對象(encode):

>>> unicodestring = u"我愛你">>> unicodestringu"xcexd2xb0xaexc4xe3">>> unicodestring.__class__ #另外使用isinstance(unicodestring, unicode)也可以查看是否是unicode字元串>>> utf8string = unicodestring.encode("utf-8")>>> utf8string"xc3x8exc3x92xc2xb0xc2xaexc3x84xc3xa3">>> utf8string.__class__>>> isostring = unicodestring.encode("ISO-8859-1")>>> isostring"xcexd2xb0xaexc4xe3">>> isostring.__class__>>> utf16string = unicodestring.encode("utf-16")>>> utf16string"xffxfexcex00xd2x00xb0x00xaex00xc4x00xe3x00">>> utf16string.__class__

str對象轉unicode(decode):

>>> unistring1 = unicode(utf8string, "utf-8")>>> unistring1u"xcexd2xb0xaexc4xe3">>> unistring1.__class__>>> unistring2 = unicode(isostring, "ISO-8859-1") >>> unistring2u"xcexd2xb0xaexc4xe3">>> unistring2.__class__>>> unistring3 = unicode(utf16string, "utf-16") >>> unistring3u"xcexd2xb0xaexc4xe3">>> unistring3.__class__

s.decode方法和u.encode方法是最常用的,簡單說來就是,python內部表示字元串用unicode(其實python內部的表示和真實的unicode是有點差別的,對我們幾乎透明,可不考慮),和人交互的時候用str對象。s.decode -------->將s解碼成unicode,參數指定的是s本來的編碼方式。這個和unicode(s,encodename)是一樣的。u.encode -------->將unicode編碼成str對象,參數指定使用的編碼方式。助記:decode to unicode from parameterencode to parameter from unicode

只有decode方法和unicode構造函數可以得到unicode對象。上述最常見的用途是比如這樣的場景,我們在python源文件中指定使用編碼cp936,# coding=cp936或#-*- coding:cp936 -*-或#coding:cp936的方式(不寫默認是ascii編碼)這樣在源文件中的str對象就是cp936編碼的,我們要把這個字元串傳給一個需要保存成其他編碼的地方(比如xml的utf-8,excel需要的utf-16)通常這麼寫:strobj.decode("cp936").encode("utf-16")

3、外部編碼: 一般不必要為字元串內在的表示擔憂; 只有當嘗試把Unicode傳遞給給一些基於位元組的函數的時候,Unicode字元的表示 變成一個議題, 比如文件的write方法或網路套接字的send 方法。那時,你必須要選擇該如何表示這些(Unicode) 字元為位元組。從Unicode碼到位元組串的轉換被叫做編碼。同樣地,當你從文件,套接字或其他的基於位元組的對象 中裝入一個Unicode字元串的時候,你需要把位元組串解碼為(Unicode)字元。

示例:

>>> f = open("/home/spider/atdc/data/alias/case1/pack", "r") >>> data = f.read()>>> data.__class__>>> msg = u"">>> msg += dataTraceback (most recent call last): File "", line 1, in UnicodeDecodeError: "ascii" codec can"t decode byte 0xd5 in position 835: ordinal not in range(128)

msg被初始化為unicode字元串,而data是read函數返回的文件內容,是str對象,msg要粘結data就需要編碼轉換了:

>>> msg += unicode(data, "ISO-8859-1")

另外跟標準輸出打交道時也需要考慮編碼轉換:

>>> print msgTraceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 835-844: ordinal not in range(128)

有人建議重設sys的defaultencoding,是否能奏效呢?

>>> import sys>>> reload(sys)>>> sys.setdefaultencoding("utf8")>>> print msg Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 835-844: ordinal not in range(128)

哦,跟之前一樣的錯誤。那setdefaultencoding起了什麼樣的作用呢?繼續看下面的示例:

>>> import sys>>> sys.getdefaultencoding()"ascii">>> u = u"我愛你">>> uu"xcexd2xb0xaexc4xe3">>> u.decode("utf8") #unicode不能用utf8解碼?!Traceback (most recent call last): File "", line 1, in File "/home/spider/bin/lib/python2.6/encodings/utf_8.py", line 16, in decode return codecs.utf_8_decode(input, errors, True)UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-5: ordinal not in range(128)>>> reload(sys)>>> sys.setdefaultencoding("utf8")>>> u.decode("utf8") u"xcexd2xb0xaexc4xe3"

其實unicode已經是python支持的最底層的字元串格式了,再對其解碼的話就使用sys的defaultencoding對結果進行編碼。 而reload之前sys的defaultencoding是ascii,ascii只支持0-127的英文字元串的編碼格式,所以無法編碼中文。而reload 之後重設只是將解碼後的unicode再用unicode編碼,所以結果跟解碼前的一樣。

另外一些環境變數也會影響python的編碼:

export LANG=zh_CN.gbk #之前LAGN=C>>> u = u"我愛你">>> uu"u6211u7231u4f60">>> print u #這個時候就可以直接print了我愛你>>> s = "我愛你" >>> s"xcexd2xb0xaexc4xe3">>> print s我愛你>>> s.decode("gbk") #跟變數u的內容一樣,說明此時的unicode是用gbk編碼的u"u6211u7231u4f60">>> import sys>>> sys.getdefaultencoding()"ascii">>> sys.stdout.encoding"GBK">>> sys.getfilesystemencoding()"GBK"

外部編碼更進一步的例子可以參考:http://blog.csdn.net/jiyucn/archive/2008/02/16/2100006.aspx

4、其他話題: (1)插入非法字元:

一個字元串中不一定都是統一編碼的,如讀取一個網頁,head部分是utf8編碼,而body部分卻可能是gb編碼,或者由於 截斷的緣故一些字元串已經不完整了,這個時候再做編碼操作就可能出錯。如:

>>> s = "我愛你" >>> s"xcexd2xb0xaexc4xe3">>> serr = "xcexd2xb0xaexc4xe3xa1">>> s.decode("gbk")u"u6211u7231u4f60">>> serr.decode("gbk")Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: "gbk" codec can"t decode byte 0xa1 in position 6: incomplete multibyte sequencegbk無法編碼第6個字元0xa1。這個時候我們可以指定decode的第2個參數為"ignore":>>> serr.decode("gbk", "ignore")u"u6211u7231u4f60"

使用ignore可以忽視那些錯誤字元,可選項還有strict(預設),replace(會替換成一個合適的字元,往往是編碼集外 的字元),如:

export LANG=zh_CN.gbk>>> serr = "xcexd2xb0xaexc4xe3xa1">>> serr.decode("gbk", "replace") u"u6211u7231u4f60ufffd">>> print serr.decode("gbk", "replace") Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "gbk" codec can"t encode character u"ufffd" in position 3: illegal multibyte sequence對多數編碼集來說字元u"ufffd"都是非法字元。

(2)編碼判斷:

A、利用chardet識別編碼:

#從標準輸入讀取字元串,重新編碼,輸出到標準輸出import sysimport chardetdata=sys.stdin.read()incodec=chardet.detect(data)["encoding"]data=data.decode(incodec)data=data.encode("utf-8")sys.stdout.write(data)

唯一麻煩的是chardet不是python的標準庫,需要自己手動安裝。

B、利用系統信息,但會有例外:

import sysreload(sys)sys.setdefaultencoding("utf8")class OutputWrapper: """進行控制台輸出編碼轉換的封裝方案""" input_enc = sys.getdefaultencoding() output_enc = sys.getfilesystemencoding() def __init__(self, target): self.target = target self.buffer = "" def flush(self): self.target.flush() def write(self, message): lines = message.split("
") lines[0] = self.buffer + lines[0] self.buffer = lines.pop() for line in lines: try: line = line.decode(self.input_enc).encode(self.output_enc) + "
" except: line = line + "
" self.target.write(line)sys.stdout = OutputWrapper(sys.__stdout__)sys.stderr = OutputWrapper(sys.__stderr__)

局限性:

1、這個方案只是簡單的假設至少在字元串的每一行中,採用的是相同的編碼,或者是 sys.getdefaultencoding(), 或者是 sys.getfilesystemencoding()。 2、這個方案假設終端輸出採用的編碼和本地文件系統採用的編碼是一樣的(通常如此但總有例外)。

C、自己動手豐衣足食:

def zh2unicode(stri): """Auto converter encodings to unicode It will test utf8,gbk,big5,jp,kr to converter""" for c in ("utf-8", "gbk", "big5", "jp","euc_kr","utf16","utf32"): try: return stri.decode(c) except: pass return stri

該函數將含中文字元的str對象轉換為unicode,但是不知道哪種編碼合適,就遍歷地decode。局限性就在於遍歷集不 一定全,而且遍歷集過大的話會影響性能。 背景:在實際數據處理中,我們或多或少會接觸到中文,如兩個dc pack包的diff。使用python對中文數據 處理難免會遇到編碼問題。

python裡面主要考慮三種編碼:

1、源文件編碼:

如果我們在源文件中使用中文注釋或中文docstring或中文字元串,如不明確指定應使用哪個中文字符集,解 釋器將無法處理我們的程序。這是因為解釋器默認程序使用的是ASCII或ISO-8859-1(即LATIN-1)編碼。

解決方法是在文件頭部使用coding聲明(往往緊跟在#!注釋行後面):

#coding: gbk或# coding=gbk或# -*- coding: gbk -*-

2、內部編碼: python內部表示一個字元串有兩種方式:一種是普通的str對象(基於位元組的字元表示),另一種是unicode字元 串。它們之間可相互轉換:

unicode轉其他編碼形式的str對象(encode):

>>> unicodestring = u"我愛你">>> unicodestringu"xcexd2xb0xaexc4xe3">>> unicodestring.__class__ #另外使用isinstance(unicodestring, unicode)也可以查看是否是unicode字元串>>> utf8string = unicodestring.encode("utf-8")>>> utf8string"xc3x8exc3x92xc2xb0xc2xaexc3x84xc3xa3">>> utf8string.__class__>>> isostring = unicodestring.encode("ISO-8859-1")>>> isostring"xcexd2xb0xaexc4xe3">>> isostring.__class__>>> utf16string = unicodestring.encode("utf-16")>>> utf16string"xffxfexcex00xd2x00xb0x00xaex00xc4x00xe3x00">>> utf16string.__class__

str對象轉unicode(decode):

>>> unistring1 = unicode(utf8string, "utf-8")>>> unistring1u"xcexd2xb0xaexc4xe3">>> unistring1.__class__>>> unistring2 = unicode(isostring, "ISO-8859-1") >>> unistring2u"xcexd2xb0xaexc4xe3">>> unistring2.__class__>>> unistring3 = unicode(utf16string, "utf-16") >>> unistring3u"xcexd2xb0xaexc4xe3">>> unistring3.__class__

s.decode方法和u.encode方法是最常用的,簡單說來就是,python內部表示字元串用unicode(其實python內部的表示和真實的unicode是有點差別的,對我們幾乎透明,可不考慮),和人交互的時候用str對象。s.decode -------->將s解碼成unicode,參數指定的是s本來的編碼方式。這個和unicode(s,encodename)是一樣的。u.encode -------->將unicode編碼成str對象,參數指定使用的編碼方式。助記:decode to unicode from parameterencode to parameter from unicode

只有decode方法和unicode構造函數可以得到unicode對象。上述最常見的用途是比如這樣的場景,我們在python源文件中指定使用編碼cp936,# coding=cp936或#-*- coding:cp936 -*-或#coding:cp936的方式(不寫默認是ascii編碼)這樣在源文件中的str對象就是cp936編碼的,我們要把這個字元串傳給一個需要保存成其他編碼的地方(比如xml的utf-8,excel需要的utf-16)通常這麼寫:strobj.decode("cp936").encode("utf-16")

3、外部編碼: 一般不必要為字元串內在的表示擔憂; 只有當嘗試把Unicode傳遞給給一些基於位元組的函數的時候,Unicode字元的表示 變成一個議題, 比如文件的write方法或網路套接字的send 方法。那時,你必須要選擇該如何表示這些(Unicode) 字元為位元組。從Unicode碼到位元組串的轉換被叫做編碼。同樣地,當你從文件,套接字或其他的基於位元組的對象 中裝入一個Unicode字元串的時候,你需要把位元組串解碼為(Unicode)字元。

示例:

>>> f = open("/home/spider/atdc/data/alias/case1/pack", "r") >>> data = f.read()>>> data.__class__>>> msg = u"">>> msg += dataTraceback (most recent call last): File "", line 1, in UnicodeDecodeError: "ascii" codec can"t decode byte 0xd5 in position 835: ordinal not in range(128)

msg被初始化為unicode字元串,而data是read函數返回的文件內容,是str對象,msg要粘結data就需要編碼轉換了:

>>> msg += unicode(data, "ISO-8859-1")

另外跟標準輸出打交道時也需要考慮編碼轉換:

>>> print msgTraceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 835-844: ordinal not in range(128)

有人建議重設sys的defaultencoding,是否能奏效呢?

>>> import sys>>> reload(sys)>>> sys.setdefaultencoding("utf8")>>> print msg Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "ascii" codec can"t encode characters in position 835-844: ordinal not in range(128)

哦,跟之前一樣的錯誤。那setdefaultencoding起了什麼樣的作用呢?繼續看下面的示例:

>>> import sys>>> sys.getdefaultencoding()"ascii">>> u = u"我愛你">>> uu"xcexd2xb0xaexc4xe3">>> u.decode("utf8") #unicode不能用utf8解碼?!Traceback (most recent call last): File "", line 1, in File "/home/spider/bin/lib/python2.6/encodings/utf_8.py", line 16, in decode return codecs.utf_8_decode(input, errors, True)UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-5: ordinal not in range(128)>>> reload(sys)>>> sys.setdefaultencoding("utf8")>>> u.decode("utf8") u"xcexd2xb0xaexc4xe3"

其實unicode已經是python支持的最底層的字元串格式了,再對其解碼的話就使用sys的defaultencoding對結果進行編碼。 而reload之前sys的defaultencoding是ascii,ascii只支持0-127的英文字元串的編碼格式,所以無法編碼中文。而reload 之後重設只是將解碼後的unicode再用unicode編碼,所以結果跟解碼前的一樣。

另外一些環境變數也會影響python的編碼:

export LANG=zh_CN.gbk #之前LAGN=C>>> u = u"我愛你">>> uu"u6211u7231u4f60">>> print u #這個時候就可以直接print了我愛你>>> s = "我愛你" >>> s"xcexd2xb0xaexc4xe3">>> print s我愛你>>> s.decode("gbk") #跟變數u的內容一樣,說明此時的unicode是用gbk編碼的u"u6211u7231u4f60">>> import sys>>> sys.getdefaultencoding()"ascii">>> sys.stdout.encoding"GBK">>> sys.getfilesystemencoding()"GBK"

外部編碼更進一步的例子可以參考:http://blog.csdn.net/jiyucn/archive/2008/02/16/2100006.aspx

4、其他話題: (1)插入非法字元:

一個字元串中不一定都是統一編碼的,如讀取一個網頁,head部分是utf8編碼,而body部分卻可能是gb編碼,或者由於 截斷的緣故一些字元串已經不完整了,這個時候再做編碼操作就可能出錯。如:

>>> s = "我愛你" >>> s"xcexd2xb0xaexc4xe3">>> serr = "xcexd2xb0xaexc4xe3xa1">>> s.decode("gbk")u"u6211u7231u4f60">>> serr.decode("gbk")Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: "gbk" codec can"t decode byte 0xa1 in position 6: incomplete multibyte sequencegbk無法編碼第6個字元0xa1。這個時候我們可以指定decode的第2個參數為"ignore":>>> serr.decode("gbk", "ignore")u"u6211u7231u4f60"

使用ignore可以忽視那些錯誤字元,可選項還有strict(預設),replace(會替換成一個合適的字元,往往是編碼集外 的字元),如:

export LANG=zh_CN.gbk>>> serr = "xcexd2xb0xaexc4xe3xa1">>> serr.decode("gbk", "replace") u"u6211u7231u4f60ufffd">>> print serr.decode("gbk", "replace") Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: "gbk" codec can"t encode character u"ufffd" in position 3: illegal multibyte sequence對多數編碼集來說字元u"ufffd"都是非法字元。

(2)編碼判斷:

A、利用chardet識別編碼:

#從標準輸入讀取字元串,重新編碼,輸出到標準輸出import sysimport chardetdata=sys.stdin.read()incodec=chardet.detect(data)["encoding"]data=data.decode(incodec)data=data.encode("utf-8")sys.stdout.write(data)

唯一麻煩的是chardet不是python的標準庫,需要自己手動安裝。

B、利用系統信息,但會有例外:

import sysreload(sys)sys.setdefaultencoding("utf8")class OutputWrapper: """進行控制台輸出編碼轉換的封裝方案""" input_enc = sys.getdefaultencoding() output_enc = sys.getfilesystemencoding() def __init__(self, target): self.target = target self.buffer = "" def flush(self): self.target.flush() def write(self, message): lines = message.split("
") lines[0] = self.buffer + lines[0] self.buffer = lines.pop() for line in lines: try: line = line.decode(self.input_enc).encode(self.output_enc) + "
" except: line = line + "
" self.target.write(line)sys.stdout = OutputWrapper(sys.__stdout__)sys.stderr = OutputWrapper(sys.__stderr__)

局限性:

1、這個方案只是簡單的假設至少在字元串的每一行中,採用的是相同的編碼,或者是 sys.getdefaultencoding(), 或者是 sys.getfilesystemencoding()。 2、這個方案假設終端輸出採用的編碼和本地文件系統採用的編碼是一樣的(通常如此但總有例外)。

C、自己動手豐衣足食:

def zh2unicode(stri): """Auto converter encodings to unicode It will test utf8,gbk,big5,jp,kr to converter""" for c in ("utf-8", "gbk", "big5", "jp","euc_kr","utf16","utf32"): try: return stri.decode(c) except: pass return stri

該函數將含中文字元的str對象轉換為unicode,但是不知道哪種編碼合適,就遍歷地decode。局限性就在於遍歷集不 一定全,而且遍歷集過大的話會影響性能。
推薦閱讀:

將NSString轉換成UTF8編碼的NSString
徹底搞懂編碼 GBK 和 UTF8
引得市缺字字體編碼方案說明
iOS KVO crash 自修復技術實現與原理解析

TAG:編碼 |