Python 2.x 中 Unicode 的使用

由于 Python 在 1989 年被創(chuàng)造出來,Python 2 發(fā)布于 2000 年,這時 Unicode 還沒有被廣泛應(yīng)用,所以 Python 2 中對于寬字符集的先天支持不夠完善,使用過程中會有些容易誤解的地方。本文以中文處理為例,說下對于寬字符集的處理。

先說結(jié)論:

  • 所有 Python 源文件的文件頭,加上 # -*- coding: UTF-8 -*-# coding: u8
  • 所有源文件保存為 UTF-8 編碼
  • 代碼中所有帶有中文的字面值字符串,都使用 Unicode 字符串,如:s = u'測試中文'
  • 從文本文件中讀取的內(nèi)容,如果需要對其中的字符做編輯處理,需要在打開文件時指定編碼,或者當做字節(jié)流加載后,轉(zhuǎn)為 Unicode 字符串處理。
  • 將字符串內(nèi)容保存到文件時,先指定要使用的編碼,將編碼后得到的數(shù)據(jù)寫入文件

細說

一、理想狀態(tài)

近幾年相對新一些的開發(fā)語言,已經(jīng)對 Unicode 有了完善支持,個人理解整體的處理思路是這樣的:
  1、通過對源文件字符編碼格式處理能力的增強,編譯器可以自動處理源文件的編碼格式,或者默認約定為 UTF-8,來減少這方面帶來的干擾;
  2、內(nèi)存中的字符串,不存在所謂的編碼問題,只是各個字符序號的一個序列;
  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 支持不夠完善的情況下,以下幾方面都容易引入問題:

  1. 源文件編碼格式(建議統(tǒng)一使用 UTF-8)
  2. 編譯器對于源文件格式的識別和處理(這個作為代碼編寫者無法干預,只能按照規(guī)則執(zhí)行)
  3. 編譯器對于源碼中寬字符字面值的理解
  4. 外部數(shù)據(jù)的格式,如:外部文件、從網(wǎng)絡(luò)獲取的數(shù)據(jù)

這幾種情況中:
  1、2 可以通過規(guī)范約定,能掃清很多干擾。
  3 比較容易處理,按照語言規(guī)則,讓編譯器按照 Unicode 去處理字面值中的寬字符(如中文)的處理,類似這樣

s = u'測試中文'
# 這樣編寫,字符串 s 就是按照 Unicode 處理其中內(nèi)容的

4 是真正要注意的情況。這部分內(nèi)容不受編程時代碼的約束,完全決定于外部環(huán)境。對于對 Unicode 支持良好的開發(fā)環(huán)境,獲取數(shù)據(jù)時,會保存到一個二進制字節(jié)流中,當轉(zhuǎn)換為字符串表示時,需要指定字符編碼,然后才能做轉(zhuǎn)換,每一步都很清晰,而且拿到的字符串,一定是 Unicode。而在 Python 2 中,沒有這樣的強制要求,所以就需要自己處理:

# -*- coding: UTF-8 -*-
# 打開文件并讀取內(nèi)容
fp = open('file.txt', 'r')
data = fp.read()
print type(data)
# 根據(jù)數(shù)據(jù)實際的編碼格式,轉(zhuǎn)換為 Unicode 字符串,再進行使用
s = data.decode('gbk')
print type(s)
print s
四、規(guī)則約定總結(jié)

本文開頭,作為結(jié)論提出了一些約定的規(guī)范做法,這里再做個總結(jié)。

  1. 所有 Python 源文件的文件頭,加上 # -*- coding: UTF-8 -*-# coding: u8
  2. 所有源文件保存為 UTF-8 編碼
  3. 代碼中所有帶有中文的字面值字符串,都使用 Unicode 字符串,如:
# -*- coding: UTF-8 -*-
myStr = u'測試中文'
print myStr
print type(myStr)
print len(myStr)
  1. 從文本文件中讀取的內(nèi)容,如果需要對其中的字符做編輯處理,需要在打開文件時指定編碼,或者當做字節(jié)流加載后,轉(zhuǎn)為 Unicode 字符串處理。
# -*- coding: UTF-8 -*-
# 打開文件并讀取內(nèi)容
fp = open('file.txt', 'r')
data = fp.read()
print type(data)
# 根據(jù)數(shù)據(jù)實際的編碼格式,轉(zhuǎn)換為 Unicode 字符串,再進行使用
s = data.decode('gbk')
print type(s)
print s
  1. 將字符串內(nèi)容保存到文件時,先指定要使用的編碼,將編碼后得到的數(shù)據(jù)寫入文件
# -*- coding: UTF-8 -*-
s = u'我的中文測試'         # 帶有中文的 Unicode 字符串
data = s.encode('UTF-8')  # 使用指定編碼,轉(zhuǎn)成數(shù)據(jù)
# 將數(shù)據(jù)寫入文件
fp = open('file.txt', 'w')
fp.write(data)
fp.close()
五、案例

Requests 是 Python 中一個強大的的網(wǎng)絡(luò)庫,在寫一些爬蟲工具時會用到。在網(wǎng)絡(luò)請求完成后,會拿到一個 response 對象。一般情況,通過 response.text 返回的 Unicode 字符串就可以滿足要求。
  最近寫的一個爬蟲工具,就在編碼部分出了問題。網(wǎng)站一部分頁面是 UTF-8 編碼,另一部分是 GBK 編碼。開始的時候并不知道,統(tǒng)一使用 response.text 來做處理,但是發(fā)現(xiàn)一些冷僻字出現(xiàn)了亂碼。

問題原因
response 同時提供了 content 屬性和 text 屬性。其中:

  • response.content 屬性類型為 str,保存著原始內(nèi)容的字節(jié)流
  • response.text 屬性類型為 unicode,是從 response.content 內(nèi)容解碼得到的
    網(wǎng)站的 UTF-8 部分頁面,直接用 response.text 獲取沒有問題。但是對于 GBK 編碼的那部分頁面,恰巧冷僻字比較多,Requests 庫在解碼得到 response.text 的時候,內(nèi)部使用了不完善的中文字符集,個人猜測可能是 GB2312 之類的,導致一些字符不能識別,需要用 GBK 解碼解決。

解決方法

# 對于確定返回內(nèi)容為 GBK 編碼的情況,通過 GBK 解碼,得到原始的 Unicode
response.content.decode('GBK')

參考鏈接:
Python中的str與unicode處理方法
Python編碼格式說明及轉(zhuǎn)碼函數(shù)encode和decode的使用

(完)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容