python中文亂碼不著急,先看懂字節(jié)和字符
Python2.x使用過程中,中文亂碼解決最耳熟能詳?shù)姆椒ň褪窃诖a前加上#-*- coding:utf-8 –*-
那么為什么需要這么做呢?什么又是字節(jié)和字符?下面我們了解下。
我來講一下字符問題我的理解吧,雖然我對(duì)Python的編碼處理的具體細(xì)節(jié)還不太清楚,不過臨時(shí)稍微看了一下,和Perl的原理也差不多
最重要的是必須區(qū)分“字符”和“字節(jié)”的不同,“字符”是抽象的,而“字節(jié)”是具體的
比如一個(gè)“中”字,在不同編碼中用如下字節(jié)表示:
GBK Big5 UTF-8 UTF-16LE
\xD6\xD0 \xA4\xA4 \xE4\xB8\xAD \x2D\x4E
所謂“抽象”的“字符”的“中”,并不是指“\xD6\xD0”或“\xA4\xA4”或任何字節(jié),應(yīng)該把它理解成:GBK編碼中“\xD6\xD0”字節(jié)所指代的那個(gè)字符(語言學(xué)中的能指→所指),或者UTF-8編碼中“\xE4\xB8\xAD”所指代的那個(gè)字符,但并不是這些具體字節(jié)本身
問題是,抽象的字符要作為數(shù)據(jù)進(jìn)行存儲(chǔ)和傳遞,就必須有具體的形式,也就是說你在程序內(nèi)部實(shí)現(xiàn)中,要存儲(chǔ)“中”這個(gè)字符,你必須采用某些特定的字節(jié)。你可以用“\xD6\xD0”,也可以用“\xE4\xB8\xAD”,也可以用“\x2D\x4E”,Python在Windows下采用的是UTF-16LE(?),也就意味著它的“字符”的載體編碼是UTF-16LE
sys.setdefaultencoding(name) Set the current default string encoding used by the Unicode implementation.
文檔上是這么寫的,如果我的理解沒錯(cuò)的話,這個(gè)函數(shù)的作用就是改變“字符”的載體編碼,sys.setdefaultencoding('gbk')以后,“中”這個(gè)字符在程序內(nèi)部就不是用“\x2D\x4E”來承載,而是用“\xD6\xD0”來承載了
Python2.x里的str和unicode有什么區(qū)別呢?從字面意義上看容易混淆,實(shí)際上,你可以把它理解成str是“字節(jié)串”,unicode是“字符串”(string總是翻譯成“字符串”,在這里就很容易把人繞暈),看下面的例子:
# -*- coding: gb2312 -*- s = "張三李四" print len(s) #=> 8 u = s.decode('gbk') print len(u) #=> 4
我的腳本編碼用的是GBK,而不是UTF-8,你會(huì)看到len(s)是8,這是這四個(gè)漢字所用的實(shí)際8個(gè)“字節(jié)”,而len(u)是4,這就表示這里有4個(gè)“字符”
encode和decode是什么意思呢?所謂編碼,就是把意義轉(zhuǎn)換成符號(hào);而解碼,就是把符號(hào)還原成意義。在這里,encode應(yīng)該理解成把抽象的字符轉(zhuǎn)換成具體的字節(jié),而decode是把具體的字節(jié)還原成抽象的字符
現(xiàn)在的問題是:str類和unicode類都同時(shí)具有encode和decode方法,這是一個(gè)讓我很不以為然的設(shè)定。如果按照字節(jié)與字符的區(qū)分,encode方法是應(yīng)該只歸unicode類所有,decode方法是只歸str類所有的,因?yàn)椤耙饬x”只能轉(zhuǎn)換成“符號(hào)”,“意義”再還原成“意義”這本身就沒有意義。
假如我們這樣:
# -*- coding: gb2312 -*- s = "張三李四" u = s.decode('gbk') # 沒問題,字節(jié)解碼為字符,符號(hào)還原為意義 s2 = s.encode('gbk') # 出錯(cuò)了!字節(jié)沒法再編碼成字節(jié),除非s全部是ASCII字符,但是這樣s2和s是完全等同的,這個(gè)操作有什么意義? u2 = u.decode('gbk') # 又出錯(cuò)了!也只能u只包含ASCII字符,u2和u也是完全等同,這個(gè)操作也沒有意義
在這里提一下Perl的處理方式,我不知道Python處理編碼的原理是否是直接得自Python,還是說這是各門語言共同的做法(但是Ruby又不是這樣做的),總之Python2.x是有缺陷的
Perl里只有一種string,它實(shí)際也區(qū)分字符串和字節(jié)串(以UTF-8作為底層的承載編碼),但不像Python2.x分str和unicode,而是string內(nèi)部有一個(gè)utf8的flag,這個(gè)flag是on的時(shí)候,這個(gè)string就是一個(gè)“字符”串,這個(gè)flag是off的時(shí)候就是一個(gè)“字節(jié)”串,它的編碼、解碼函數(shù)如下:
$octets = encode(ENCODING, $string [, CHECK])
$string = decode(ENCODING, $octets [, CHECK])
$octets就是字節(jié)串,$string就是字符串,也就是說,encode只對(duì)$string起作用,而decode只對(duì)$octets起作用,不像Python是str和unicode兩類兩個(gè)方法都有,但是其實(shí)各有一個(gè)是沒用的。LarryWall是語言學(xué)家,他設(shè)計(jì)的這一套字符、字節(jié)關(guān)系是完全符合語言學(xué)中的“能指-所指”理論的,而GvR恐怕就對(duì)語言學(xué)不在行了,Python的處理就不怎么精妙了。
再來說一下file.write為什么有編碼問題:
# -*- coding: gb2312 -*- s = "張三李四" u = s.decode('gbk') f = open('text.txt','w') f.write(u) # 出錯(cuò)! f.write(u.encode('gbk')) # 這樣才行
出錯(cuò)的原因很簡(jiǎn)單,你想輸出的是“字符”,而不是“字節(jié)”。上面說過,“字符”是抽象的,你是沒有辦法把一個(gè)抽象的東西寫到文件里去的。雖然抽象的字符下面肯定是有具體的承載字節(jié)的,但是Python似乎并不愿意把unicode底層的字節(jié)跟IO攪在一起,這就導(dǎo)致f.write(a_unicode)的失敗,當(dāng)然a_unicode假如只包含ASCII字符,這個(gè)可以成功,然而這是一種捷徑,是一條讓人越來越糊涂的捷徑
然后再是u標(biāo)記的意義是什么?很簡(jiǎn)單,就是自動(dòng)完成字節(jié)→字符的轉(zhuǎn)換
# -*- coding: gb2312 -*- s_or_u1 = "張三李四" print type(s_or_u1) #=> <type 'str'> s_or_u2 = u"張三李四" print type(s_or_u2) #=> <type 'unicode'>
u"張三李四"就相當(dāng)于"張三李四".decode(a_enc),這里的a_enc就是#coding行設(shè)定的gb2312
不得不說,(不知是不是從Perl得來的)這套字符處理方式很晦澀,字符、字節(jié)區(qū)分的概念實(shí)在不太容易理解,而Python本身的細(xì)節(jié)處理也沒有做好,Perl做得很干凈了,都不容易理解,Python沒做干凈更不行了。另外再附贈(zèng)簡(jiǎn)單介紹Ruby的字符處理方式,跟Perl完全不同:
Ruby中沒有字符、字節(jié)的區(qū)分,一切字符串都是“帶有一個(gè)編碼屬性的字節(jié)串”。因?yàn)闆]有抽象的字符,所以就沒有字節(jié)→字符的轉(zhuǎn)換,也就根本沒有、也不需要decode方法,Ruby的String類只有encode方法。因?yàn)闆]有抽象的“字符”概念,Ruby的編碼問題應(yīng)該比Perl、Python容易理解。沒有“字符”的還有一個(gè)好處是:處理多字節(jié)文本無需經(jīng)過中間轉(zhuǎn)換。你要在Perl里處理中文字符,來源文件是GBK編碼的,實(shí)際都得先轉(zhuǎn)換成UTF-8,Perl才能處理:Python要先轉(zhuǎn)化成UTF-16才能處理。對(duì)于海量文本來說,這一轉(zhuǎn)換過程肯定是要耗費(fèi)一定的資源的。而Ruby不需要這種轉(zhuǎn)換,直接就能處理GBK或其他編碼了??赡苓@樣做也是考慮了日文的實(shí)際,日文的shift-jis(?)是本土編碼,根本都不跟ASCII兼容,不像GBK是跟ASCII兼容的,這樣做就不必轉(zhuǎn)換就能處理土著編碼的文檔了。如果說Perl的字符-字節(jié)區(qū)分是語言學(xué)家的學(xué)院派做法的話,Ruby就是契合了多字節(jié)字符處理需要的實(shí)用派做法。
總結(jié)
以上就是本文關(guān)于python中文亂碼不著急,先看懂字節(jié)和字符的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
Keras模型轉(zhuǎn)成tensorflow的.pb操作
這篇文章主要介紹了Keras模型轉(zhuǎn)成tensorflow的.pb操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07python實(shí)現(xiàn)簡(jiǎn)易版學(xué)生成績(jī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)簡(jiǎn)易版學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Python網(wǎng)絡(luò)爬蟲神器PyQuery的基本使用教程
這篇文章主要給大家介紹了關(guān)于Python網(wǎng)絡(luò)爬蟲神器PyQuery的基本使用教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)使用PyQuery具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02淺談tensorflow中幾個(gè)隨機(jī)函數(shù)的用法
今天小編就為大家分享一篇淺談tensorflow中幾個(gè)隨機(jī)函數(shù)的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07Opencv常見圖像格式Data Type及代碼實(shí)例
這篇文章主要介紹了Opencv常見圖像格式Data Type及代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11python3對(duì)拉勾數(shù)據(jù)進(jìn)行可視化分析的方法詳解
這篇文章主要給大家介紹了關(guān)于python3對(duì)拉勾數(shù)據(jù)進(jìn)行可視化分析的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python3具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04解決pandas無法在pycharm中使用plot()方法顯示圖像的問題
今天小編就為大家分享一篇解決pandas無法在pycharm中使用plot()方法顯示圖像的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-05-05Python調(diào)用ChatGPT制作基于Tkinter的桌面時(shí)鐘
這篇文章主要為大家詳細(xì)介紹了Python如何調(diào)用ChatGPT制作基于Tkinter的桌面時(shí)鐘,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-03-03