由于 Python 在 1989 年被創(chuàng)造出來债朵,Python 2 發(fā)布于 2000 年子眶,這時 Unicode 還沒有被廣泛應用,所以 Python 2 中對于寬字符集的先天支持不夠完善序芦,使用過程中會有些容易誤解的地方臭杰。本文以中文處理為例,說下對于寬字符集的處理谚中。
先說結論:
- 所有 Python 源文件的文件頭渴杆,加上
# -*- coding: UTF-8 -*-
或# coding: u8
- 所有源文件保存為 UTF-8 編碼
- 代碼中所有帶有中文的字面值字符串,都使用 Unicode 字符串藏杖,如:
s = u'測試中文'
- 從文本文件中讀取的內容将塑,如果需要對其中的字符做編輯處理,需要在打開文件時指定編碼蝌麸,或者當做字節(jié)流加載后点寥,轉為 Unicode 字符串處理。
- 將字符串內容保存到文件時来吩,先指定要使用的編碼敢辩,將編碼后得到的數(shù)據寫入文件
細說
一蔽莱、理想狀態(tài)
近幾年相對新一些的開發(fā)語言,已經對 Unicode 有了完善支持戚长,個人理解整體的處理思路是這樣的:
1盗冷、通過對源文件字符編碼格式處理能力的增強,編譯器可以自動處理源文件的編碼格式同廉,或者默認約定為 UTF-8仪糖,來減少這方面帶來的干擾;
2迫肖、內存中的字符串锅劝,不存在所謂的編碼問題,只是各個字符序號的一個序列蟆湖;
3故爵、所謂的 UTF-xx 編碼這樣的說法,只是用于存儲和傳輸過程的需要隅津。UTF 即 Unicode Transformation Format诬垂,定義也說明了其用途。
二伦仍、歷史包袱
對于歷史比較悠久的開發(fā)語言或者開發(fā)工具结窘,由于歷史原因,當年還沒有 Unicode呢铆,所以語言缺乏對于 Unicode 原生的支持晦鞋,一般會通過類庫來做擴展進行支持。個人接觸過的開發(fā)工具有:Delphi 7 及之前版本(之后的我沒用過)中的 Object Pascal棺克、Python 2悠垛。(插句題外話,突然覺得 C 當年是多么機靈娜谊,竟然沒有引入字符串類型确买,沒給自己找麻煩,一切處理交給類庫纱皆,隨時升級湾趾,嘖嘖嘖……)
以 Python 2 為例,當初的字符串是個字節(jié)流派草,其中每個字節(jié)表示一個 ASCII 碼搀缠。而且實際上,即便一些字節(jié)超出 ASCII 范圍近迁,也一樣能放到字符串中艺普。所以,遇到寬字符集中的字符,一樣可以用字符串表示歧譬,但是計算字符串長度岸浑、獲取特定位置字符時,就會出現(xiàn)問題:
# Python REPL 環(huán)境中的測試
>>> s = '測試中文'
>>> print s, type(s), len(s)
測試中文 <type 'str'> 12
這時就需要一種支持 Unicode 的字符串類型作為彌補:
# Python REPL 環(huán)境中的測試
>>> s = u'測試中文'
>>> print s, type(s), len(s)
測試中文 <type 'unicode'> 4
三瑰步、引起混亂的原因
個人認為矢洲,在開發(fā)環(huán)境對 Unicode 支持不夠完善的情況下,以下幾方面都容易引入問題:
- 源文件編碼格式(建議統(tǒng)一使用 UTF-8)
- 編譯器對于源文件格式的識別和處理(這個作為代碼編寫者無法干預缩焦,只能按照規(guī)則執(zhí)行)
- 編譯器對于源碼中寬字符字面值的理解
- 外部數(shù)據的格式读虏,如:外部文件、從網絡獲取的數(shù)據
這幾種情況中:
1舌界、2 可以通過規(guī)范約定掘譬,能掃清很多干擾泰演。
3 比較容易處理呻拌,按照語言規(guī)則,讓編譯器按照 Unicode 去處理字面值中的寬字符(如中文)的處理睦焕,類似這樣
s = u'測試中文'
# 這樣編寫藐握,字符串 s 就是按照 Unicode 處理其中內容的
4 是真正要注意的情況。這部分內容不受編程時代碼的約束垃喊,完全決定于外部環(huán)境猾普。對于對 Unicode 支持良好的開發(fā)環(huán)境,獲取數(shù)據時本谜,會保存到一個二進制字節(jié)流中初家,當轉換為字符串表示時,需要指定字符編碼乌助,然后才能做轉換溜在,每一步都很清晰,而且拿到的字符串他托,一定是 Unicode掖肋。而在 Python 2 中,沒有這樣的強制要求赏参,所以就需要自己處理:
# -*- coding: UTF-8 -*-
# 打開文件并讀取內容
fp = open('file.txt', 'r')
data = fp.read()
print type(data)
# 根據數(shù)據實際的編碼格式志笼,轉換為 Unicode 字符串,再進行使用
s = data.decode('gbk')
print type(s)
print s
四把篓、規(guī)則約定總結
本文開頭纫溃,作為結論提出了一些約定的規(guī)范做法,這里再做個總結韧掩。
- 所有 Python 源文件的文件頭紊浩,加上
# -*- coding: UTF-8 -*-
或# coding: u8
- 所有源文件保存為 UTF-8 編碼
- 代碼中所有帶有中文的字面值字符串,都使用 Unicode 字符串,如:
# -*- coding: UTF-8 -*-
myStr = u'測試中文'
print myStr
print type(myStr)
print len(myStr)
- 從文本文件中讀取的內容郎楼,如果需要對其中的字符做編輯處理万伤,需要在打開文件時指定編碼,或者當做字節(jié)流加載后呜袁,轉為 Unicode 字符串處理敌买。
# -*- coding: UTF-8 -*-
# 打開文件并讀取內容
fp = open('file.txt', 'r')
data = fp.read()
print type(data)
# 根據數(shù)據實際的編碼格式,轉換為 Unicode 字符串阶界,再進行使用
s = data.decode('gbk')
print type(s)
print s
- 將字符串內容保存到文件時虹钮,先指定要使用的編碼,將編碼后得到的數(shù)據寫入文件
# -*- coding: UTF-8 -*-
s = u'我的中文測試' # 帶有中文的 Unicode 字符串
data = s.encode('UTF-8') # 使用指定編碼膘融,轉成數(shù)據
# 將數(shù)據寫入文件
fp = open('file.txt', 'w')
fp.write(data)
fp.close()
五芙粱、案例
Requests 是 Python 中一個強大的的網絡庫,在寫一些爬蟲工具時會用到氧映。在網絡請求完成后春畔,會拿到一個 response 對象。一般情況岛都,通過 response.text 返回的 Unicode 字符串就可以滿足要求律姨。
最近寫的一個爬蟲工具,就在編碼部分出了問題臼疫。網站一部分頁面是 UTF-8 編碼择份,另一部分是 GBK 編碼。開始的時候并不知道烫堤,統(tǒng)一使用 response.text 來做處理荣赶,但是發(fā)現(xiàn)一些冷僻字出現(xiàn)了亂碼。
問題原因
response 同時提供了 content 屬性和 text 屬性鸽斟。其中:
-
response.content
屬性類型為 str拔创,保存著原始內容的字節(jié)流 -
response.text
屬性類型為 unicode,是從response.content
內容解碼得到的
網站的 UTF-8 部分頁面湾盗,直接用response.text
獲取沒有問題伏蚊。但是對于 GBK 編碼的那部分頁面,恰巧冷僻字比較多格粪,Requests 庫在解碼得到response.text
的時候躏吊,內部使用了不完善的中文字符集,個人猜測可能是 GB2312 之類的帐萎,導致一些字符不能識別比伏,需要用 GBK 解碼解決。
解決方法
# 對于確定返回內容為 GBK 編碼的情況疆导,通過 GBK 解碼赁项,得到原始的 Unicode
response.content.decode('GBK')
參考鏈接:
Python中的str與unicode處理方法
Python編碼格式說明及轉碼函數(shù)encode和decode的使用
(完)