黃聰:解決python中文處理亂碼,先要弄懂“字元”和“位元組”的差別

來源:互聯網
上載者:User

轉載來自:http://hcsem.com/2095/

我來講一下字元問題我的理解吧,雖然我對Python的編碼處理的具體細節還不太清楚,不過臨時稍微看了一下,和Perl的原理也差不多
  
最重要的是必須區分“字元”和“位元組”的不同,“字元”是抽象的,而“位元組”是具體的
  
比如一個“中”字,在不同編碼中用如下位元組表示:
  
    GBK      Big5        UTF-8     UTF-16LE
\xD6\xD0  \xA4\xA4  \xE4\xB8\xAD  \x2D\x4E
  

所謂“抽象”的“字元”的“中”,並不是指“\xD6\xD0”或“\xA4\xA4”或任何位元組,應該把它理解成:GBK編碼中“\xD6\xD0”字
節所指代的那個字元(語言學中的能指→所指),或者UTF-8編碼中“\xE4\xB8\xAD”所指代的那個字元,但並不是這些具體位元組本身
  

問題是,抽象的字元要作為資料進行儲存和傳遞,就必須有具體的形式,也就是說你在程式內部實現中,要儲存“中”這個字元,你必須採用某些特定的位元組。你可
以用“\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.
  
文檔上是這麼寫的,如果我的理解沒錯的話,這個函數的作用就是改變“字元”的載體編碼,sys.setdefaultencoding('gbk')以後,“中”這個字元在程式內部就不是用“\x2D\x4E”來承載,而是用“\xD6\xD0”來承載了
  
Python2.x裡的str和unicode有什麼區別呢?從字面意義上看容易混淆,實際上,你可以把它理解成str是“位元組串”,unicode是“字串”(string總是翻譯成“字串”,在這裡就很容易把人繞暈),看下面的例子:
  
# -*- coding: gb2312 -*-
  
s = "張三李四"
print len(s) #=> 8
u = s.decode('gbk')
print len(u) #=> 4
  
我的指令碼編碼用的是GBK,而不是UTF-8,你會看到len(s)是8,這是這四個漢字所用的實際8個“位元組”,而len(u)是4,這就表示這裡有4個“字元”
  
encode和decode是什麼意思呢?所謂編碼,就是把意義轉換成符號;而解碼,就是把符號還原成意義。在這裡,encode應該理解成把抽象的字元轉換成具體的位元組,而decode是把具體的位元組還原成抽象的字元
  

現在的問題是:str類和unicode類都同時具有encode和decode方法,這是一個讓我很不以為然的設定。如果按照位元組與字元的區
分,encode方法是應該只歸unicode類所有,decode方法是只歸str類所有的,因為“意義”只能轉換成“符號”,“意義”再還原成“意
義”這本身就沒有意義。
  
假如我們這樣:
  
# -*- coding: gb2312 -*-
  
s = "張三李四"
u = s.decode('gbk') # 沒問題,位元組解碼為字元,符號還原為意義
s2 = s.encode('gbk')
  # 出錯了!位元組沒法再編碼成位元組,除非s全部是ASCII字元,但是這樣s2和s是完全等同的,這個操作有什麼意義?
u2 = u.decode('gbk')
  # 又出錯了!也只能u只包含ASCII字元,u2和u也是完全等同,這個操作也沒有意義
  
在這裡提一下Perl的處理方式,我不知道Python處理編碼的原理是否是直接得自Python,還是說這是各門語言共同的做法(但是Ruby又不是這樣做的),總之Python2.x是有缺陷的
  

Perl裡只有一種string,它實際也區分字串和位元組串(以UTF-8作為底層的承載編碼),但不像Python2.x分str和unicode,
而是string內部有一個utf8的flag,這個flag是on的時候,這個string就是一個“字元”串,這個flag是off的時候就是一個
“位元組”串,它的編碼、解碼函數如下:
  
$octets = encode(ENCODING, $string [, CHECK])
  
$string = decode(ENCODING, $octets [, CHECK])
  

$octets就是位元組串,$string就是字串,也就是說,encode只對$string起作用,而decode只對$octets起作用,不像
Python是str和unicode兩類兩個方法都有,但是其實各有一個是沒用的。Larry
Wall是語言學家,他設計的這一套字元、位元組關係是完全符合語言學中的“能指-所指”理論的,而GvR恐怕就對語言學不在行了,Python的處理就不
怎麼精妙了。
  
再來說一下file.write為什麼有編碼問題:
  
# -*- coding: gb2312 -*-
  
  
s = "張三李四"
u = s.decode('gbk')
  
f = open('text.txt','w')
f.write(u) # 出錯!
f.write(u.encode('gbk')) # 這樣才行
  

出錯的原因很簡單,你想輸出的是“字元”,而不是“位元組”。上面說過,“字元”是抽象的,你是沒有辦法把一個抽象的東西寫到檔案裡去的。雖然抽象的字元下
面肯定是有具體的承載位元組的,但是Python似乎並不願意把unicode底層的位元組跟IO攪在一起,這就導致f.write(a_unicode)的
失敗,當然a_unicode假如只包含ASCII字元,這個可以成功,然而這是一種捷徑,是一條讓人越來越糊塗的捷徑
  
然後再是u標記的意義是什嗎?很簡單,就是自動完成位元組→字元的轉換
  
# -*- coding: gb2312 -*-
  
s_or_u1 = "張三李四"
print type(s_or_u1) #=> <type 'str'>
  
s_or_u2 = u"張三李四"
print type(s_or_u2) #=> <type 'unicode'>
  
u"張三李四"就相當於"張三李四".decode(a_enc),這裡的a_enc就是#coding行設定的gb2312
  

不得不說,(不知是不是從Perl得來的)這套字元處理方式很晦澀,字元、位元組區分的概念實在不太容易理解,而Python本身的細節處理也沒有做
好,Perl做得很乾淨了,都不容易理解,Python沒做乾淨更不行了。另外再附贈簡單介紹Ruby的字元處理方式,跟Perl完全不同:
  

Ruby中沒有字元、位元組的區分,一切字串都是“帶有一個編碼屬性的位元組串”。因為沒有抽象的字元,所以就沒有位元組→字元的轉換,也就根本沒有、也不需
要decode方法,Ruby的String類只有encode方法。因為沒有抽象的“字元”概念,Ruby的編碼問題應該比Perl、Python容易
理解。沒有“字元”的還有一個好處是:處理多位元組文本無需經過中間轉換。你要在Perl裡處理中文字元,來源檔案是GBK編碼的,實際都得先轉換成
UTF-8,Perl才能處理:Python要先轉化成UTF-16才能處理。對于海量文本來說,這一轉換過程肯定是要耗費一定的資源的。而Ruby不需
要這種轉換,直接就能處理GBK或其他編碼了。可能這樣做也是考慮了日文的實際,日文的shift-jis(?)是本土編碼,根本都不跟ASCII相容,
不像GBK是跟ASCII相容的,這樣做就不必轉換就能處理土著編碼的文檔了。如果說Perl的字元-位元組區分是語言學家的學院派做法的話,Ruby就是
契合了多位元組字元處理需要的實用派做法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.