程序員都自視清高,以爲本身是創造者,常常鄙視不太懂技術的產品或者QA。可悲的是,程序員之間也相互鄙視,程序員的鄙視鏈流傳甚廣,做爲一個Python程序員,天然最關心的是下面這幅圖啦html
咱們項目組一值使用Python2.7,雖然咱們也知道Python3的諸多好處,也曾經蠢蠢欲動過,但因爲各類歷史緣由,以及業務的壓力,咱們只可能繼續使用Python2.7。更悲哀的是,咱們組不是那麼international,因此代碼中仍是涉及到大量的中文,所以偶爾也會遇到亂碼以及UnicodeError,因而生活在了鄙視鏈的末端。python
所以,本文的目標是解釋清楚python2.7中unicode、str的編解碼關係,力求在鄙視鏈中前進一步。程序員
注意:本文實驗主要基於win7,Python2.7;以及Linux ,Python2.7。除非特殊說明,全部的命令都是在終端中交互式輸入;若是沒有強調平臺,那麼就是window上的結果。下面是一些默認的環境信息(其重要性後文會介紹)windows
windows網絡
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
>>> sys.stdin.encoding
'cp936'
>>> sys.stdout.encoding
'cp936'
>>> sys.getfilesystemencoding()
'mbcs'python2.7
注意,上面CP936是GBK的別名,在https://docs.python.org/2/library/codecs.html#standard-encodings 能夠查看。編輯器
Linux函數
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'UTF-8')
>>> sys.stdin.encoding
'UTF-8'
>>> sys.stdout.encoding
'UTF-8'
>>> sys.getfilesystemencoding()
'UTF-8'ui
本文地址:http://www.cnblogs.com/xybaby/p/7814299.html編碼
首先來講一說gbk gb2312 unicode utf-8這些術語,這些術語與語言無關。
計算機的世界只有0和1,所以任何字符(也就是實際的文字符號)也是由01串組成。計算機爲了運算方便,都是8個bit組成一個字節(Byte),字符表達的最小單位就是字節,即一個字符佔用一個或者多個字節。字符編碼(character encoding)就是字集碼,編碼就是將字符集中的字符映射爲一個惟一二進制的過程。
計算機發源於美國,使用的是英文字母(字符),全部26個字母的大小寫加上數字0到10,加上符號和控制字符,總數也很少,用一個字節(8個bit)就能表示全部的字符,這就是ANSI的「Ascii」編碼(American Standard Code for Information Interchange,美國信息互換標準代碼)。好比,小寫字母‘a’的ascii 碼是01100001,換算成十進制就是97,十六進制就是0x61。計算機中,通常都是用十六進制來描述字符編碼。
可是當計算機傳到中國的時候,ASCII編碼就行不通了,漢字這麼多,一個字節確定表示不下啊,因而有了GB 2312(中國國家標準簡體中文字符集)。GB2312使用兩個字節來對一個字符進行編碼,其中前面的一個字節(稱之爲高字節)從0xA1用到 0xF7,後面一個字節(低字節)從0xA1到0xFE,GB2312能表示幾千個漢字,並且與asill嗎也是兼容的。
但後來發現,GB2312仍是不夠用,因而進行擴展,產生了GBK(即漢字內碼擴展規範), GBK同Gb2312同樣,兩個字節表示一個字符,但區別在於,放寬了對低字節的要求,所以能表示的範圍擴大到了20000多。後來,爲了容納少數名族,以及其餘漢字國家的文字,出現了GB13080。GB13080是兼容GBK與GB2312的,能容納更多的字符,與GBK與GB2312不一樣的是,GB18030採用單字節、雙字節和四字節三種方式對字符編碼
所以,就咱們關心的漢字而言,三種編碼方式的表示範圍是:
GB18030 》 GBK 》 GB2312
即GBK是GB2312的超集,GB1803又是GBK的超集。後面也會看到,一個漢字能夠用GBK表示,但不必定能被GB2312所表示
固然,世界上還有更多的語言與文字,每種文字都有本身的一套編碼規則,這樣一旦跨國就會出現亂碼,亟待一個全球統一的解決辦法。這個時候ISO(國際標準化組織)出馬了,發明了"Universal Multiple-Octet Coded Character Set」,簡稱 UCS, 俗稱 「unicode」。目標很簡單:廢了全部的地區性編碼方案,從新搞一個包括了地球上全部文化、全部字母和符號 的編碼!
unicode每種語言中的每一個字符設定了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求。unicode編碼必定以\u開頭。
可是,unicode只是一個編碼規範,是全部字符對應二進制的集合,而不是具體的編碼規則。或者說,unicode是表現形式,而不是存儲形式,就是說沒用定義每一個字符是如何以二進制的形式存儲的。這個就跟GBK這些不同,GBK是表裏以下,表現形式即存儲形式。
好比漢字「嚴」的unicode編碼是\u4e25,對應的二進制是1001110 00100101,可是當其通過網絡傳輸或者文件存儲時,是無法知道怎麼解析這些二進制的,容易和其餘字節混在一塊兒。那麼怎麼存儲unicode呢,因而出現了UTF(UCS Transfer Format),這個是具體的編碼規則,即UTF的表現形式與存儲格式是同樣的。
所以,能夠說,GBK和UTF-8是同一個層面的東西,跟unicode是另外一個層面的東西,unicode飄在空中,若是要落地,須要轉換成utf-8或者GBK。只不過,轉換成Utf-8,你們都能懂,更懂用,而轉換成GBK,只有中國人才看得懂
UTF也有不一樣的實現,如UTF-8, UTF-16, 這裏以UTF-8爲例進行講解(下面一小節引用了阮一峯的文章)。
UTF-8最大的一個特色,就是它是一種變長的編碼方式。它可使用1~4個字節表示一個符號,根據不一樣的符號而變化字節長度。UTF-8的編碼規則很簡單,只有二條:
1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。所以對於英語字母,UTF-8編碼和ASCII碼是相同的。
2)對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一概設爲10。剩下的沒有說起的二進制位,所有爲這個符號的unicode碼。
下表總結了編碼規則,字母x表示可用編碼的位。
Unicode符號範圍 | UTF-8編碼方式 (十六進制) | (二進制) ----------------------+--------------------------------------------- 0000 0000-0000 007F | 0xxxxxxx 0000 0080-0000 07FF | 110xxxxx 10xxxxxx 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以漢字「嚴」爲例,演示如何實現UTF-8編碼。
已知「嚴」的unicode是4E25(100111000100101),根據上表,能夠發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),所以「嚴」的UTF-8編碼須要三個字節,即格式是「1110xxxx 10xxxxxx 10xxxxxx」。而後,從「嚴」的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就獲得了,「嚴」的UTF-8編碼是「11100100 10111000 10100101」,轉換成十六進制就是E4B8A5。
下面使用Python語言來驗證上面的理論。在這一章節中,當提到unicode,通常是指unicode type,即Python中的類型;也會提到unicode編碼、unicode函數,請你們注意區別。
另外,對於編碼,也有兩種意思。第一個是名字,指的是字符的二進制表示,如unicode編碼、gbk編碼。第二個是動詞,指的是從字符到二進制的映射過程。不過後文中,編碼做爲動詞,狹義理解爲從unicode類型轉換成str類型的過程,解碼則是相反的過程。另外強調的是,unicode類型必定是unicode編碼,而str類型多是gbk、ascii或者utf-8編碼。
在python2.7中,有兩種「字符串」類型,分別是str 與 unicode,他們有同一個基類basestring。str是plain string,其實應該稱之爲字節串,由於是每個字節換一個單位長度。而unicode就是unicode string,這纔是真正的字符串,一個字符(可能多個字節)算一個單位長度。
python2.7中,unicode類型須要在文本之間加u表示。
>>> us = u'嚴'
>>> print type(us), len(us)
<type 'unicode'> 1
>>> s = '嚴'
>>> print type(s), len(s)
<type 'str'> 2
>>>
從上能夠看到,第一,us、s的類型是不同的;其二,同一個漢字,不一樣的類型其長度也是不同的,對於unicode類型的實例,其長度必定是字符的個數,而對於str類型的實例,其長度是字符對應的字節數目。這裏強調一下,s(s = '嚴')的長度在不一樣的環境下是不同的!後文會解釋
這是python中兩個magic method,很容易讓新手迷糊,由於不少時候,兩者的實現是同樣的,可是這兩個函數是用在不一樣的地方
_str__, 主要是用於展現,str(obj)或者print obj的時候調用,返回值必定是一個str 對象
__repr__, 是被repr(obj), 或者在終端直接打obj的時候調用
>>> us = u'嚴'
>>> us
u'\u4e25'
>>> print us
嚴
能夠看到,不使用print返回的是一個更能反映對象本質的結果,即us是一個unicode對象(最前面的u表示,以及unicode編碼是用的\u),且「嚴」的unicode編碼確實是4E25。而print調用可us.__str__,等價於print str(us),使得結果對用戶更友好。那麼unicode.__str__是怎麼轉換成str的呢,答案會在後面揭曉
前面已經提到,unicode只是編碼規範(只是字符與二進制的映射集合),而utf-8是具體的編碼規則(不只包含字符與二進制的映射集合,並且映射後的二進制是能夠用於存儲和傳輸的),即utf-8負責把unicode轉換成可存儲和傳輸的二進制字符串即str類型,咱們稱這個轉換過程爲編碼。而從str類型到unicode類型的過程,咱們稱之爲解碼。
Python中使用decode()和encode()來進行解碼和編碼,以unicode類型做爲中間類型。以下圖所示
decode encode
str ---------> unicode --------->str
即str類型調用decode方法轉換成unicode類型,unicode類型調用encode方法轉換成str類型。for example
>>> us = u'嚴'
>>> ss = us.encode('utf-8')
>>> ss
'\xe4\xb8\xa5'
>>> type(ss)
<type 'str'>
>>> ss.decode('utf-8') == us
True
從上能夠看出encode與decode兩個函數的做用,也能夠看出'嚴'的utf8編碼是E4B8A5。
就是說咱們使用unicode.encode將unicode類型轉換成了str類型,在上面也提到unicode.__str__也是將unicode類型轉換成str類型。兩者有什麼卻比呢
首先看看文檔
str.encode([encoding[, errors]])
Return an encoded version of the string. Default encoding is the current default string encoding.
object.__str__(self)
Called by the str() built-in function and by the print statement to compute the 「informal」 string representation of an object.
注意:str.encode 這裏的str是basestring,是str類型與unicode類型的基類
能夠看到encode方法是有可選的參數:encoding 和 errors,在上面的例子中encoding即爲utf-8;而__str__是沒有參數的,咱們能夠猜測,對於unicode類型,__str__函數必定也是使用了某種encoding來對unicode進行編碼。
首先不由要問,若是encode方法沒有帶入參數,是什麼樣子的:
>>> us.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e25' in position 0: ordinal not in range(128)
不難看出,默認使用的就是ascii碼來對unicode就行編碼,爲何是ascii碼,其實就是系統默認編碼(sys.getdefaultencoding的返回值)。ascii碼顯然沒法表示漢字,因而拋出了異常。而使用utf-8編碼的時候,因爲utf可以表示這個漢字,因此沒報錯。
若是直接打印ss(us.encode('utf-8')的返回值)會怎麼樣
>>> print ss
涓
結果略有些奇怪,us.__str__(即直接打印us)的結果不同,那麼試試encoding = gbk呢?
>>> print us.encode('gbk')
嚴
U got it! 事實上也是如此,python會採用終端默認的編碼(用locale.getdefaultlocale()查看,windows是爲gbk)將unicode編碼成str類型。
在Linux(終端編碼爲utf-8),結果以下:
>>> us= u'嚴'
>>> print us.encode('utf-8')
嚴
>>> print us.encode('gbk')
▒▒
>>> print us
嚴
>>>
注意上面的亂碼!
在上上小節,介紹了unicode能夠經過utf-8編碼(encoding = utf-8),轉換成utf-8表示的str,在上一節也能夠看出unicode也能夠經過gbk編碼(encoding=gbk),轉換成gbk表示的str。這裏有點暈,留做第一個問題,後面解釋
unicode與utf8之間的相互轉換能夠計算得知,但unicode與gbk之間的相互轉換沒有計算公式,就只能靠查表了,就是說有一張映射表,有某一個漢字對應的unicode表示與gbk表示的映射關係
>>> us = u'嚴'
>>> us
u'\u4e25'
>>> us.encode('gbk')
'\xd1\xcf'
>>> us.encode('gb2312')
'\xd1\xcf'
>>> us.encode('gb18030')
'\xd1\xcf'
>>> s = '嚴'
>>> s
'\xd1\xcf'
>>>
從上不難看出,嚴的unicdoe編碼是4e25,GBK編碼是d1cf,所以us經過gbk編碼就是d1cf。一樣也能看到,GB18030,GBK,GB2312是兼容的
ss = us.encode('utf-8'), ss是一個str類型,直接打印結果有點奇怪,一個「涓」字,那一個str類型的「涓」是哪些二進制組成的呢
>>> s = '涓'
>>> s
'\xe4\xb8'
能夠看到,str類型的「涓」,其二進制是E4B8,跟'嚴'的utf8編碼(E4B8A5)相差了一個A5,那麼就是由於A5顯示不出來,驗證以下:
>>> print '--%s--' % ss
--涓?-
所以,只是碰巧顯示了「涓」而已,事實上ss跟「」涓「」毫無關係
在上上小節,提到了utf-8編碼的str,與gbk編碼的str,感受有點繞。咱們知道,一個漢字‘嚴’,可存儲的編碼格式能夠是gbk('\xd1\xcf'),也能夠是utf-8('\xe4\xb8\xa5'),那麼當咱們在終端敲入這個漢字的時候,是哪種格式呢?取決於終端默認編碼。
windows上(默認終端編碼爲gbk):
>>> s = '嚴'
>>> s
'\xd1\xcf'
Linux上(默認終端編碼爲utf-8):
>>> a = '嚴'
>>> a
'\xe4\xb8\xa5'
一樣一個漢字,一樣都是Python中的str類型,在不一樣的編碼格式下,其二進制是不同的。所以,其長度也是不同的,對於str類型,其長度是對應的字節長度。
也能看出gbk編碼的字節長度通常小於utf-8,這也是gbk繼續存在的一個緣由。
這裏,要強調一下,unicode的二進制形式是與終端的編碼格式無關的!這個也不難理解。
str類型到unicode類型的轉換,出了上面提到的str.decode,還有一個unicode函數。兩個函數的簽名爲:
unicode(object[, encoding[, errors]])
Return the Unicode string version of object using one of the following modes:
str.decode([encoding[, errors]])
Decodes the string using the codec registered for encoding. encoding defaults to the default string encoding.
兩者參數相同,事實上兩者是等價的,encoding的默認值也是同樣的,都是sys.getdefaultencoding()的結果。for example:
>>> s = '嚴'
>>> newuse = unicode(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
>>> newuse = unicode(s, 'utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd1 in position 0: invalid continuation byte
>>> newuse = unicode(s, 'gbk')
>>> newuse
u'\u4e25'
第一個UnicodeDecodeError,就是由於系統默認的編碼是asill嗎;第二個UnicodeDecodeError,是由於,s(str類型的實例)的編碼取決於終端默認編碼(即windows下的gbk),爲了能打印出來,也就必須用gbk編碼來表示這個str,所以只能查詢gbk與unicode的映射表將s轉換成unicode類型。
在諸多Python代碼中,都會看到這麼一段:
1 import sys 2 reload(sys) 3 sys.setdefaultencoding('utf-8')
不難猜測,setdefaultencoding跟getdefaultencoding是配對的,爲啥要將系統的默認編碼設置成utf-8,其實就是解決str到unicode的轉換問題。
上一小節已經提到過,使用unicode函數將str類型轉換成unicode類型時,要考慮兩個因素:第一,str自己是什麼編碼的;第二,若是沒有傳入encoding參數,默認使用sys.getdefaultencoding。encoding參數必須與str自己的編碼對應,不然就是UnicodeDecodeError。
寫python代碼的程序都知道,咱們要在py文件第一行寫上:
# -*- coding: utf-8 -*-
這句話的做用在於,告訴編輯器,該文件裏面的全部str都採用utf-8編碼,且存儲文件的時候也是使用utf-8格式。
而後文件中就會使用下面的這種代碼。
s='中文'
us=unicode(s)
使用unicode強制轉換的時候,都不習慣帶參數,爲了保證encoding參數必須與str自己的編碼一致,因此使用setdefaultencoding將系統默認編碼設置爲utf-8
下面介紹幾種常見的亂碼與異常UnicodeError, 大多數亂碼或者異常的緣由在前面已經講過了,同時,對於一些亂碼,也試圖給出可行的解決辦法。
UnicodeError包括UnicodeDecodeError 與UnicodeEncodeError ,前者是decode也就是str轉unicode的時候出了異常,後者則是encode也就是unicode轉str的時候出了異常。
例子就是上面反覆提到的例子
>>> ss = us.encode('utf-8')
>>> print ss
涓
若是一個str類型來自網絡或者文件讀取,最好先按照對端encode的方式先decode成unicode,而後再輸出(輸出的時候會自動轉換成指望終端支持的編碼格式的str)
直接上例子
>>> newus = u'囍'
>>> newus
u'\u56cd'
>>> newus.encode('gbk')
'\x87\xd6'
>>> newus.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'gb2312' codec can't encode character u'\u56cd' in position 0: illegal multibyte sequence
>>>
能夠看到,‘囍’字能夠被gbk編碼,可是不能被gb2312編碼。
在上面講unicode函數的時候已經舉過例子,會爆出UnicodeDecodeError 異常。
這個錯誤比較的緣由,更多來自str到unicode的默認轉換,好比一個str與一個unicode相加的時候:
>>> a = '嚴'
>>> b = u'嚴'
>>> c = a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
unicode 與 str相加,str會轉換爲unicode,使用默認的unicode(strobj, encoding = sys.getdefaultencoding())
某些狀況下,咱們打印出一個str類型,看到結果是'\\u4e25', 或者'\u4e25',對於這個字符串,是否是很眼熟,不錯, ‘嚴‘的unicode編碼就是u'\u4e25'。仔細一看,只是在引號前面多了一個u(表示是一個unicode類型)。那麼當咱們看到一個'\u4e25'的時候,怎麼知道對應的漢字是什麼?對於已知的這種格式的str,天然能夠手動加一個u,而後在終端輸出,可是若是是一個變量,須要自動轉換成unicode呢,這個時候就可使用python-specific-encodings中的unicode_escape
>>> s = '\u4e25'
>>> s
'\\u4e25'
>>> us = s.decode('unicode_escape')
>>> us
u'\u4e25'
有時候,也會看到相似這樣的str,'\\xd1\\xcf', 看起來也很熟悉,跟漢字「嚴」的gbk編碼'\xd1\xcf'很像,區別在於前者多了一個‘\’, 這樣就沒法解釋成一個十六進制了。解決辦法是python-specific-encodings中的string_escape
>>> s='\\xd1\\xcf'
>>> s
'\\xd1\\xcf'
>>> print s
\xd1\xcf>>> news = s.decode('string_escape')
>>> news
'\xd1\xcf'
>>> print news
嚴
在這裏留下一個問題:
u'嚴' == '嚴'
返回值是True 仍是 False呢?固然這裏故意省去了上下文環境,不過明確的說,在不一樣的編碼環境下,答案是不同的,緣由都在上文中!
無論怎麼樣解釋,python2.x中的字符編碼仍是一件讓人頭疼的事情,即便搞懂了,以後遇到了也可能忘記。對於這個問題,諸多建議以下:
第一:使用python3,就不用再糾結str於unicode了;可是這個很難開發者說了算;
第二:不要使用中文,註釋什麼的都用英文;理想很豐滿,現實很難,只是致使大量的拼音;
第三:對於中文字符串,不要用str表示,而是用unicode表示;現實中也很差實施,你們都不肯意多寫一個u
第四:只在傳輸,或者持久化的時候對unicode進行encode,相反的過程時decode
第五:對於網絡接口,約定好編解碼格式,強烈建議使用utf-8
第六:看到UnicodeXXXError不要慌,若是XXX是Encode,那麼必定是unicode轉str的時候出了問題;若是是Decode,必定是str轉unicode的時候出了問題。