Python中的編碼與解碼(轉)


Python中的字符編碼與解碼困擾了我很久了,一直沒有認真整理過,這次下靜下心來整理了一下我對方面知識的理解。
文章中對有些知識沒有做深入的探討,一是我自己也沒有去深入的了解,例如各種編碼方案的實現方式等;二是我覺得只要提能對理解Python字符編碼與解碼的關鍵知識即可,想深入可以查其它資料。
文中的觀點肯定有紕漏,只做參考,歡迎指正。

Unicode

參考:http://baike.baidu.com/view/40801.htm

Unicode是什么,這里不多說了,百科上面講的很清楚了,這里只提下有助於理解本文主題的知識。

Unicode是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。每個字符都對應一個編號,編號的范圍是0-0x10FFFF來。

字符編碼方案

參考:http://baike.baidu.com/view/40801.htm

我們知道,每個Unicode字符對應一個編號,例如漢字“我”對應的編號是25105,但在程序中不是直接用編號來表示Unicode字符的(那得有多長的數字啊),而是表示成16進制格式,但具體怎么轉換成16進制,不同的編碼方案采用的方式不一樣。

>>> s = u'我'
>>> ord(s)
25105
>>> s
u'\u6211'
>>> s.encode('utf-8')
'\xe6\x88\x91'
>>> s.encode('utf-16')
'\xff\xfe\x11b'
>>> s.encode('utf-32')
'\xff\xfe\x00\x00\x11b\x00\x00'
>>> s.encode('gbk')
'\xce\xd2'
>>>

我們用unicode()內置函數創建了一個Python中的unicode字符,然后ord()函數可以得到它在Unicode字符集中的編號。Python的unicode對象有一個encode()方法,用來對unicode對像進行編碼。

這個示例中的一些知識,在后面會講到,現在不用深究。這里提下UTF-8編碼方式,其它的還沒深入研究過,但不妨礙本文的主題。

UTF-8以字節為單位對Unicode編號進行編碼。每個字節被轉換成一個二位的十六進制數。UTF-8的特點是對不同范圍的字符使用不同長度的編碼。對於0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節。

Python支持很多的編碼方案,包括ascii,utf-8,utf-16,utf32,gbk,gb2312等,完整的列表可以在下面的鏈接中找到:
http://docs.python.org/2/library/codecs.html#standard-encodings

Python中的字符串

在Python中,str 對象表示所有普通字符串對象,它只能表示ASCII碼表中的字符,特點是每個字符占用一個字節,所以也叫做字節字符串(Byte string)。unicode 對象則可以表示所有Unicode字符集中的字符。
s = 'I love python'
u = u'我愛Python'

print isinstance(s, str)
print isinstance(u, unicode)

--輸出
True
True

還可以使用 str() 函數 和 unicode() 從一個對象構建字符串,
str(object) 函數返回的結果通常可以通過定義 object 的 str 屬性來定制返回的結果。
unicode()函數接受多個參數,與編碼格式有關,這在后面會講到。

Python中的Unicode 轉義字符

我們常看到“ \u6211” 這樣的字符,用json.dumps(obj)時,如里obj是unicode字符,包含非ASCII碼,且ensure_ascii=True,那返回的結果字符串中就包含這種形式。這是個轉義字符,表示Unicode字符“我”。但是注意的是,這種轉義只在unicode字面量中有效,用print 輸出時會自動轉為對應的unicode字符。而在str字面量中沒有特殊意義。web信息中常會遇到“\u4f60\u597d”類型的字符。首先’\u‘開頭就基本表明是跟unicode編碼相關的。python里str.decode()和str.encode()為我們提供了解碼和編碼的方法。其中str.decode('unicode_escape')能將此種字符串解碼為unicode字符串。下面是在ubuntu的ipython中的操作,

>>> a = u'你好'
>>> a
u'\u4f60\u597d'
>>> print a
你好
>>> b = '你好'
>>> b
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> print b
你好
>>> c = '\u4f60\u597d'
>>> 
>>> c
'\\u4f60\\u597d'
>>> print c
\u4f60\u597d
>>> 
>>> d = r'\u4f60\u597d'
>>> d
'\\u4f60\\u597d'
>>> print d
\u4f60\u597d
>>> d == c
True
>>> e = c.decode('unicode_escape')
>>> e
u'\u4f60\u597d'
>>> print e
你好
>>> 

其實6211是字符‘我’在Unicode字符集中的編號25105的16進制值

字符串字面量和程序中處理的字符串

在源代碼中,字符串通常用字面量來表示

u = u'我愛Python'

但字面量是給人看的,程序看到的是對字面量進行處理后的字符,而且 str 字符串和 unicode 字符串的處理方式不一樣。

unicode 字符串會將字面量中的非ASCII字符替換成Unicode轉義符,但最后的結果是與原字符串等價的。

str類型的字面量會使用設置的編碼格式進行編碼處理,最后得到的是編碼字符串(這點很重要,后面會提到)。編碼字符串與原字符串不能等同。

--腳本

-- coding: utf-8 --

s = '我愛Python'
u = u'我愛Python'
print 'encoded str: ', repr(s)
print 'escaped unicode: ', repr(u)
print 'str: ', s
print 'decoded str: ', s.decode('utf-8')
print 'unicode: ', u

--輸出
encoded str: '\xe6\x88\x91\xe7\x88\xb1Python'
escaped unicode: u'\u6211\u7231Python'
str: 鎴戠埍Python
decoded str: 我愛Python
unicode: 我愛Python

'\xe6\x88\x91\xe7\x88\xb1Python'是對 s 編碼后的編碼字符串,直接輸出編碼字符串會得到不一樣的結果,因為實際上,'\xe6' 等被當作16進制轉義字符來處理了。要想得到正確結果,需要先解碼。

u'\u6211\u7231Python'是轉義后的與 u 等價的unicode字符串

編碼字符串

編碼字符串,是指采用指定的編碼格式對字符進行編碼后得到的字符串。編碼格式有很多中,例如 ascii、utf-8、gbk、gbk2312等。

編碼字符串是純 str 字符串,它表示原字符串的編碼結果。直接輸出編碼字符串可能會與原來的字符串表示的值不一樣,除非原來的字符串都是ASCII字符。

--腳本

-- coding: utf-8 --

s = '我'
s1 = '我愛Python'
print len(s)
print repr(s)
print s
print repr(s1)
print s1

--輸出
3
'\xe6\x88\x91'

'\xe6\x88\x91\xe7\x88\xb1Python'
鎴戠埍Python

'\xe6\x88\x91' 和 '\xe6\x88\x91\xe7\x88\xb1Python' 就是編碼字符串。在編碼字符串中類似 '\xe6' 這種字符是Python中的16進制轉義字符,被看作是一個字符,而不是4個字符。(Python轉義序列:http://docs.python.org/2/reference/lexical_analysis.html#string-literals)

我們可以看到 s 的長度已經是3了,因為這里統計的是編碼字符串的長度。

上面的例子有個小細節,字符串只包含單個字符的時候,print語句好像做了解碼處理能直接輸出正確的結果,但多個字符就會亂碼。

這是為什么呢?

開始編碼和解碼

前面介紹了一些基本知識,現在開始來對字符串進行編碼和解碼了。

編碼:

--腳本

-- coding: utf-8 --

u = u'我愛Python'
print 'encoded[utf-8]: ', repr(u.encode('utf-8'))
print 'encoded[gbk]: ', repr(u.encode('gbk'))
print 'encoded[ascii]: ', repr(u.encode('ascii'))

--輸出
encoded[utf-8]: '\xe6\x88\x91\xe7\x88\xb1Python'
encoded[gbk]: '\xce\xd2\xb0\xaePython'
encoded[ascii]:

Traceback (most recent call last):
File "C:\Users\chw\Desktop\encoding.py", line 8, in
print 'encoded[ascii]: ', repr(u.encode('ascii'))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

在上面的例子中,我們對一個unicode字符串采用了不同編碼方式進行了編碼,打印編碼字符串。最后我們得到了一個錯誤,是因為ascii編碼格式不能編碼非ASCII字符。

對於str類型的字面量程序會自動做編碼處理,所以就不要再去編一次碼了。至於程序會采用何種編碼格式要看設置,例如,在前面的腳本中,開頭都有一個編碼格式聲明

-- coding: utf-8 --

這個聲明告訴編譯器該用什么編碼格式處理str類型的字面量。在Python2.6以后的版本好像會根據保存源代碼文件的格式來判斷編碼格式(沒有聲明的情況下),但先不深究了,總之開頭指定編碼格式應該是個好習慣。

解碼:

str對象提供decode()方法來解碼

   --腳本

-- coding: utf-8 --

s = '我愛Python'
print repr(s)
print s
print s.decode('utf-8')

--輸出
'\xe6\x88\x91\xe7\x88\xb1Python'
鎴戠埍Python #亂碼了
我愛Python

前面我們講過,str 字面量被自動編碼成編碼字符串,所以這里的 s 已經是編碼后的編碼字符串了,因此要輸出 s 原來的字符,就需要解碼。而開始我們指定了源文件的編碼方式為utf-8,所以我們需要用utf-8格式來解碼。

IDLE交互環境中的差異

在IDLE交互環境中,Unicode字面量好像不能正確的工作

>>> u = u'我愛Python'
>>> u
u'\xce\xd2\xb0\xaePython'
>>> isinstance(u, unicode)
True
>>> print u
ÎÒ°®Python
>>> 

此例中,u實際上是表示'我愛Python'的編碼字符串的unicode字符串,而不是'我愛Python'的unicode字符串了。也就是說,先將'我愛Python'編碼成編碼字符串,然后把編碼字符串轉換成unicode字符串。

在IDLE 交互環境中創建unicode對象的正確方式應該是下面這樣:

>>> u = unicode('我愛Python', 'gbk')
>>> print repr(u)
u'\u6211\u7231Python'
>>> print u
我愛Python

unicode函數的第一個參數指定編碼字符串,第二個參數指定這個編碼字符串的編碼格式。函數在處理中,先用第二個參數指定的編碼格式解碼第一個參數,根據不同的編碼格式,可以直接返回一個unicode字符串。

如果省略第二個參數,unicode函數會將ascii作為默認編碼格式(不管是交互環境還是腳本中都是這樣)。

第二個參數的值與你的環境配置有關,我在windows下面使用IDLE交互環境,默認的編碼是gbk或者是gbk兼容的編碼格式。

在腳本中unicode字面量能被解析成正確的unicode字符串,沒有IDLE那種令人費解的問題

---腳本

-- coding: utf-8 --

u = u'我愛Python'
print repr(u)
print 'unicode: ', u

---輸出

u'\u6211\u7231Python'
unicode: 我愛Python


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM