python之路金角大王_python 之路,致那些年,我们依然没搞明白的编码
本節(jié)內(nèi)容
編碼回顧
編碼轉(zhuǎn)換
Python的bytes類型
編碼回顧
在備編碼相關(guān)的課件時(shí),在知乎上看到一段關(guān)于Python編碼的回答
這哥們的這段話說的太對(duì)了,搞Python不把編碼徹底搞明白,總有一天它會(huì)猝不及防坑你一把。
不過感覺這哥們的答案并沒把編碼問題寫明白,所以只好親自動(dòng)筆了。
折騰編碼問題,有很多次,我以為自已明白了,最終發(fā)現(xiàn),那只不過是自圓其說而已,這一次,終于100%確定,動(dòng)筆即不再改!
看這篇文章前,你應(yīng)該已經(jīng)知道了為什么有編碼,以及編碼的種類情況
ASCII 占1個(gè)字節(jié),只支持英文
GB2312 占2個(gè)字節(jié),支持6700+漢字
GBK GB2312的升級(jí)版,支持21000+漢字
Shift-JIS 日本字符
ks_c_5601-1987 韓國編碼
TIS-620 泰國編碼
由于每個(gè)國家都有自己的字符,所以其對(duì)應(yīng)關(guān)系也涵蓋了自己國家的字符,但是以上編碼都存在局限性,即:僅涵蓋本國字符,無其他國家字符的對(duì)應(yīng)關(guān)系。應(yīng)運(yùn)而生出現(xiàn)了萬國碼,他涵蓋了全球所有的文字和二進(jìn)制的對(duì)應(yīng)關(guān)系,
Unicode 2-4字節(jié) 已經(jīng)收錄136690個(gè)字符,并還在一直不斷擴(kuò)張中...
Unicode 起到了2個(gè)作用:
直接支持全球所有語言,每個(gè)國家都可以不用再使用自己之前的舊編碼了,用unicode就可以了。(就跟英語是全球統(tǒng)一語言一樣)
unicode包含了跟全球所有國家編碼的映射關(guān)系,為什么呢?后面再講
Unicode解決了字符和二進(jìn)制的對(duì)應(yīng)關(guān)系,但是使用unicode表示一個(gè)字符,太浪費(fèi)空間。例如:利用unicode表示“Python”需要12個(gè)字節(jié)才能表示,比原來ASCII表示增加了1倍。
由于計(jì)算機(jī)的內(nèi)存比較大,并且字符串在內(nèi)容中表示時(shí)也不會(huì)特別大,所以內(nèi)容可以使用unicode來處理,但是存儲(chǔ)和網(wǎng)絡(luò)傳輸時(shí)一般數(shù)據(jù)都會(huì)非常多,那么增加1倍將是無法容忍的!!!
為了解決存儲(chǔ)和網(wǎng)絡(luò)傳輸?shù)膯栴},出現(xiàn)了Unicode Transformation Format,學(xué)術(shù)名UTF,即:對(duì)unicode中的進(jìn)行轉(zhuǎn)換,以便于在存儲(chǔ)和網(wǎng)絡(luò)傳輸時(shí)可以節(jié)省空間!
UTF-8: 使用1、2、3、4個(gè)字節(jié)表示所有字符;優(yōu)先使用1個(gè)字符、無法滿足則使增加一個(gè)字節(jié),最多4個(gè)字節(jié)。英文占1個(gè)字節(jié)、歐洲語系占2個(gè)、東亞占3個(gè),其它及特殊字符占4個(gè)
UTF-16: 使用2、4個(gè)字節(jié)表示所有字符;優(yōu)先使用2個(gè)字節(jié),否則使用4個(gè)字節(jié)表示。
UTF-32: 使用4個(gè)字節(jié)表示所有字符;
總結(jié):UTF 是為unicode編碼 設(shè)計(jì) 的一種 在存儲(chǔ) 和傳輸時(shí)節(jié)省空間的編碼方案。
字符在硬盤上的存儲(chǔ)
無論以什么編碼在內(nèi)存里顯示字符,存到硬盤上都是2進(jìn)制。
ascii編碼(美國):
l 0b1101100
o 0b1101111
v 0b1110110
e 0b1100101
GBK編碼(中國):
老 0b11000000 0b11001111
男 0b11000100 0b11010000
孩 0b10111010 0b10100010
Shift_JIS編碼(日本):
私 0b10001110 0b10000100
は 0b10000010 0b11001101
ks_c_5601-1987編碼(韓國):
? 0b10110011 0b10101010
? 0b10110100 0b11000010
TIS-620編碼(泰國):
??? 0b10101001 0b11010001 0b10111001
要注意的是,存到硬盤上時(shí)是以何種編碼存的,再從硬盤上讀出來時(shí),就必須以何種編碼讀,要不然就亂了。。
編碼的轉(zhuǎn)換
雖然國際語言是英語 ,但大家在自己的國家依然說自已的語言,不過出了國, 你就得會(huì)英語
編碼也一樣,雖然有了unicode and utf-8 , 但是由于歷史問題,各個(gè)國家依然在大量使用自己的編碼,比如中國的windows,默認(rèn)編碼依然是gbk,而不是utf-8
基于此,如果中國的軟件出口到美國,在美國人的電腦上就會(huì)顯示亂碼,因?yàn)樗麄儧]有g(shù)bk編碼。
若想讓中國的軟件可以正常的在 美國人的電腦上顯示,只有以下2條路可走:
讓美國人的電腦上都裝上gbk編碼
把你的軟件編碼以u(píng)tf-8編碼
第1種方法幾乎不可能實(shí)現(xiàn),第2種方法比較簡(jiǎn)單。 但是也只能是針對(duì)新開發(fā)的軟件。 如果你之前開發(fā)的軟件就是以gbk編碼的,上百萬行代碼可能已經(jīng)寫出去了,重新編碼成utf-8格式也會(huì)費(fèi)很大力氣。
so , 針對(duì)已經(jīng)用gbk開發(fā)完畢的項(xiàng)目,以上2種方案都不能輕松的讓項(xiàng)目在美國人電腦上正常顯示,難道沒有別的辦法了么?
有, 還記得我們講unicode其中一個(gè)功能是其包含了跟全球所有國家編碼的映射關(guān)系,意思就是,你寫的是gbk的“路飛學(xué)城”,但是unicode能自動(dòng)知道它在unicode中的“路飛學(xué)城”的編碼是什么,如果這樣的話,那是不是意味著,無論你以什么編碼存儲(chǔ)的數(shù)據(jù) ,只要你的軟件在把數(shù)據(jù)從硬盤讀到內(nèi)存里,轉(zhuǎn)成unicode來顯示,就可以了。
由于所有的系統(tǒng)、編程語言都默認(rèn)支持unicode,那你的gbk軟件放到美國電腦 上,加載到內(nèi)存里,變成了unicode,中文就可以正常展示啦。
這個(gè)表你自己也可以下載下來
Python3的執(zhí)行過程
在看實(shí)際代碼的例子前,我們來聊聊,python3 執(zhí)行代碼的過程
解釋器找到代碼文件,把代碼字符串按文件頭定義的編碼加載到內(nèi)存,轉(zhuǎn)成unicode
把代碼字符串按照語法規(guī)則進(jìn)行解釋,
所有的變量字符都會(huì)以u(píng)nicode編碼聲明
編碼轉(zhuǎn)換過程
實(shí)際代碼演示,在py3上 把你的代碼以u(píng)tf-8編寫, 保存,然后在windows上執(zhí)行,
s = '路飛學(xué)城'
print(s)
so ,一切都很美好,到這里,我們關(guān)于編碼的學(xué)習(xí)按說就可以結(jié)束了。
但是,如生活一樣,美好的表面下,總是隱藏著不盡如人意,上面的utf-8編碼之所以能在windows gbk的終端下顯示正常,是因?yàn)榈搅藘?nèi)存里python解釋器把utf-8轉(zhuǎn)成了unicode , 但是這只是python3, 并不是所有的編程語言在內(nèi)存里默認(rèn)編碼都是unicode,比如 萬惡的python2 就不是, 它的默認(rèn)編碼是ASCII,想寫中文,就必須聲明文件頭的coding為gbk or utf-8, 聲明之后,python2解釋器僅以文件頭聲明的編碼去解釋你的代碼,加載到內(nèi)存后,并不會(huì)主動(dòng)幫你轉(zhuǎn)為unicode,也就是說,你的文件編碼是utf-8,加載到內(nèi)存里,你的變量字符串就也是utf-8, 這意味著什么你知道么?。。。意味著,你以u(píng)tf-8編碼的文件,在windows是亂碼。
亂是正常的,不亂才不正常,因?yàn)橹挥?種情況 ,你的windows上顯示才不會(huì)亂
字符串以GBK格式顯示
字符串是unicode編碼
既然Python2并不會(huì)自動(dòng)的把文件編碼轉(zhuǎn)為unicode存在內(nèi)存里, 那就只能使出最后一招了,你自己人肉轉(zhuǎn)。Py3 自動(dòng)把文件編碼轉(zhuǎn)為unicode必定是調(diào)用了什么方法,這個(gè)方法就是,decode(解碼) 和encode(編碼)
UTF-8 --> decode 解碼 --> Unicode
Unicode --> encode 編碼 --> GBK / UTF-8 ..
decode示例
encode 示例
記住下圖規(guī)則
如何驗(yàn)證編碼轉(zhuǎn)對(duì)了呢?
查看數(shù)據(jù)類型,python 2 里有專門的unicode 類型
查看unicode編碼映射表
unicode字符是有專門的unicode類型來判斷的,但是utf-8,gbk編碼的字符都是str,你如果分辨出來的當(dāng)前的字符串?dāng)?shù)據(jù)是何種編碼的呢? 有人說可以通過字節(jié)長度判斷,因?yàn)閡tf-8一個(gè)中文占3字節(jié),gbk一個(gè)占2字節(jié)
靠上面字節(jié)個(gè)數(shù),雖然也能大體判斷是什么類型,但總覺得不是很專業(yè)。
怎么才能精確的驗(yàn)證一個(gè)字符的編碼呢,就是拿這些16進(jìn)制的數(shù)跟編碼表里去匹配。
“路飛學(xué)城”的unicode編碼的映射位置是 u'\u8def\u98de\u5b66\u57ce' ,‘\u8def’ 就是‘路’,到表里搜一下。
“路飛學(xué)城”對(duì)應(yīng)的GBK編碼是'\xc2\xb7\xb7\xc9\xd1\xa7\xb3\xc7' ,2個(gè)字節(jié)一個(gè)中文,"路" 的二進(jìn)制 "\xc2\xb7"是4個(gè)16進(jìn)制,正好2字節(jié),拿它到unicode映射表里對(duì)一下, 發(fā)現(xiàn)是G0-4237,并不是\xc2\xb7呀。。。擦。演砸了吧。。
再查下“飛” \u98de ,對(duì)應(yīng)的是G0-3749, 跟\xb7\xc9也對(duì)不上。
雖然對(duì)不上, 但好\xc2\xb7 和G0-4237中的第2位的2和第4位的7對(duì)上了,“飛”字也是一樣,莫非巧合?
把他們都轉(zhuǎn)成2進(jìn)制顯示試試
路
C 2
8 4 2 1 8 4 2 1
1 1 0 0 0 0 1 0
B 7
8 4 2 1 8 4 2 1
1 0 1 1 0 1 1 1
飛
B 7
8 4 2 1 8 4 2 1
1 0 1 1 0 1 1 1
C 9
8 4 2 1 8 4 2 1
1 1 0 0 1 0 0 1
這個(gè)“路”還是跟G0-4237對(duì)不上呀,沒錯(cuò), 但如果你把路\xc2\xb7的每個(gè)二進(jìn)制字節(jié)的左邊第一個(gè)bit變成0試試呢, 我擦,加起來就真的是4237了呀。。難道又是巧合???
必然不是,是因?yàn)?#xff0c;GBK的編碼表示形式?jīng)Q定的。。因?yàn)镚BK編碼在設(shè)計(jì)初期就考慮到了要兼容ASCII,即如果是英文,就用一個(gè)字節(jié)表示,2個(gè)字節(jié)就是中文,但如何區(qū)別連在一起的2個(gè)字節(jié)是代表2個(gè)英文字母,還是一個(gè)中文漢字呢? 中國人如此聰明,決定,2個(gè)字節(jié)連在一起,如果每個(gè)字節(jié)的第1位(也就是相當(dāng)于128的那個(gè)2進(jìn)制位)如果是1,就代表這是個(gè)中文,這個(gè)首位是128的字節(jié)被稱為高字節(jié)。 也就是2個(gè)高字節(jié)連在一起,必然就是一個(gè)中文。 你怎么如此篤定?因?yàn)?-127已經(jīng)表示了英文的絕大部分字符,128-255是ASCII的擴(kuò)展表,表示的都是極特殊的字符,一般沒什么用。所以中國人就直接拿來用了。
問:那為什么上面 "\xc2\xb7"的2進(jìn)制要把128所在的位去掉才能與unicode編碼表里的G0-4237匹配上呢?
這只能說是unicode在映射表的表達(dá)上直接忽略了高字節(jié),但真正映射的時(shí)候 ,肯定還是需要用高字節(jié)的哈。
Python bytes類型
在python 2 上寫字符串
>>> s = "路飛"
>>> print s
路飛
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e'
雖說打印的是路飛,但直接調(diào)用變量s,看到的卻是一個(gè)個(gè)的16進(jìn)制表示的二進(jìn)制字節(jié),我們?cè)趺捶Q呼這樣的數(shù)據(jù)呢?直接叫二進(jìn)制么?也可以, 但相比于010101,這個(gè)數(shù)據(jù)串在表示形式上又把2進(jìn)制轉(zhuǎn)成了16進(jìn)制來表示,這是為什么呢? 哈,為的就是讓人們看起來更可讀。我們稱之為bytes類型,即字節(jié)類型, 它把8個(gè)二進(jìn)制一組稱為一個(gè)byte,用16進(jìn)制來表示。
說這個(gè)有什么意思呢?
想告訴你一個(gè)事實(shí), 就是,python2的字符串其實(shí)更應(yīng)該稱為字節(jié)串。 通過存儲(chǔ)方式就能看出來, 但python2里還有一個(gè)類型是bytes呀,難道又叫bytes又叫字符串? 嗯 ,是的,在python2里,bytes == str , 其實(shí)就是一回事
除此之外呢, python2里還有個(gè)單獨(dú)的類型是unicode , 把字符串解碼后,就會(huì)變成unicode
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e' #utf-8
>>> s.decode('utf-8')
u'\u8def\u98de' #unicode 在unicode編碼表里對(duì)應(yīng)的位置
>>> print(s.decode('utf-8'))
路飛 #unicode 格式的字符
由于Python創(chuàng)始人在開發(fā)初期認(rèn)知的局限性,其并未預(yù)料到python能發(fā)展成一個(gè)全球流行的語言,導(dǎo)致其開發(fā)初期并沒有把支持全球各國語言當(dāng)做重要的事情來做,所以就輕佻的把ASCII當(dāng)做了默認(rèn)編碼。 當(dāng)后來大家對(duì)支持漢字、日文、法語等語言的呼聲越來越高時(shí),Python于是準(zhǔn)備引入unicode,但若直接把默認(rèn)編碼改成unicode的話是不現(xiàn)實(shí)的, 因?yàn)楹芏嘬浖褪腔谥暗哪J(rèn)編碼ASCII開發(fā)的,編碼一換,那些軟件的編碼就都亂了。所以Python 2 就直接 搞了一個(gè)新的字符類型,就叫unicode類型,比如你想讓你的中文在全球所有電腦上正常顯示,在內(nèi)存里就得把字符串存成unicode類型
>>> s = "路飛"
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e'
>>> s2 = s.decode("utf-8")
>>> s2
u'\u8def\u98de'
>>> type(s2)
時(shí)間來到2008年,python發(fā)展已近20年,創(chuàng)始人龜叔越來越覺得python里的好多東西已發(fā)展的不像他的初衷那樣,開始變得臃腫、不簡(jiǎn)潔、且有些設(shè)計(jì)讓人摸不到頭腦,比如unicode 與str類型,str 與bytes類型的關(guān)系,這給很多python程序員造成了困擾。
龜叔再也忍不了,像之前一樣的修修補(bǔ)補(bǔ)已不能讓Python變的更好,于是來了個(gè)大變革,Python3橫空出世,不兼容python2,python3比python2做了非常多的改進(jìn),其中一個(gè)就是終于把字符串變成了unicode,文件默認(rèn)編碼變成了utf-8,這意味著,只要用python3,無論你的程序是以哪種編碼開發(fā)的,都可以在全球各國電腦上正常顯示,真是太棒啦!
PY3 除了把字符串的編碼改成了unicode, 還把str 和bytes 做了明確區(qū)分, str 就是unicode格式的字符, bytes就是單純二進(jìn)制啦。
最后一個(gè)問題,為什么在py3里,把unicode編碼后,字符串就變成了bytes格式? 你直接給我直接打印成gbk的字符展示不好么?我想其實(shí)py3的設(shè)計(jì)真是煞費(fèi)苦心,就是想通過這樣的方式明確的告訴你,想在py3里看字符,必須得是unicode編碼,其它編碼一律按bytes格式展示。
好吧,就說這么多吧。
最后再提示一下,Python只要出現(xiàn)各種編碼問題,無非是哪里的編碼設(shè)置出錯(cuò)了
常見編碼錯(cuò)誤的原因有:
Python解釋器的默認(rèn)編碼
Python源文件文件編碼
Terminal使用的編碼
操作系統(tǒng)的語言設(shè)置
掌握了編碼之前的關(guān)系后,挨個(gè)排錯(cuò)就好啦
總結(jié)
以上是生活随笔為你收集整理的python之路金角大王_python 之路,致那些年,我们依然没搞明白的编码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 赊了两串冰糖葫芦才写出来的小结
- 下一篇: React--》UI组件库ant-des