Py2編碼小結

常見編碼

  • GB2312是中國規定的漢字編碼,也可以說是簡體中文的字符集編碼。GBKGB2312的擴展,除了兼容GB2312外,它還能顯示繁體中文,還有日文的假名。

  • CP936是指系統裏第936號編碼格式,即GB2312的編碼,GBKCP20936。中文版Windows中的CMD.exe,默認使用CP936

  • UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼,又稱萬國碼,由Ken Thompson於1992年創建。現在已經標準化爲RFC 3629。UTF-8用1到6個字節編碼Unicode字符。用在網頁上可以統一頁面顯示中文簡體繁體及其它語言(如英文,日文,韓文)

Unicode與UTF-8的區別

Unicode(統一碼、萬國碼、單一碼)是計算機科學領域裏的一項業界標準,包括字符集、編碼方案等。Unicode是爲了解決傳統的字符編碼方案的侷限而產生的,它爲每種語言中的每個字符設定了統一併且唯一的二進制編碼,以滿足跨語言、跨平臺進行文本轉換、處理的要求。

UTF-8是一種針對Unicode的可變長度編碼規則,主要目的是提高傳輸效率和節省磁盤空間。目前Unicode由32Bit(4字節)表示,與字符一一對應。UTF-8編碼規則

計算機通用字符編碼工作方式

如上圖所示,記事本將文件內容轉換成Unicode編碼進行處理,處理結束後再轉換成UTF-8的編碼存入文件。通常情況,在計算機內存中,統一使用Unicode編碼,當需要保存到硬盤或者需要傳輸的時候,就轉換爲UTF-8編碼。Unicode與字符一一對應,而UTF-8需要轉換計算,在內存中使用Unicode,以空間換時間提高執行效率。網絡和磁盤I/O延遲要遠大於UTF-8的轉換延遲,使用UTF-8節省帶寬,保證數據的傳輸可靠性。

Python涉及的編碼

  • 文件編碼
  • 運行環境編碼
  • Python系統編碼
  • Python程序讀取外部文件、網頁的編碼

文件編碼有什麼不同

test_utf8.py是一個以UTF-8編碼保存的py文件,通過hex工具查看文件的底層編碼,我們可以看到""的UTF-8編碼爲E5BCA0

# test_utf8.py

# -*- coding: UTF-8 -*-
s = '張'
print s
00000000h: 23 20 2D 2A 2D 20 63 6F 64 69 6E 67 3A 20 75 74 ; # -*- coding: ut
00000010h: 66 2D 38 20 2D 2A 2D 0D 0A 73 20 3D 20 27 [E5 BC ; f-8 -*-..s = '寮
00000020h: A0] 27 0D 0A 70 72 69 6E 74 20 73                ; ?..print s

新建相同內容的test_gb2312.py文件,以GB2312編碼保存並重新打開,此時""的編碼爲D5C5。比較後,發現ASCII的字符在GB2312和UTF-8的編碼下保持一致,中文字符則有不同的編碼。

00000000h: 23 20 2D 2A 2D 20 63 6F 64 69 6E 67 3A 20 75 74 ; # -*- coding: ut
00000010h: 66 2D 38 20 2D 2A 2D 0D 0A 73 20 3D 20 27 [D5 C5] ; f-8 -*-..s = '張
00000020h: 27 0D 0A 70 72 69 6E 74 20 73                   ; '..print s

Windows cmd下運行test_utf8.py和test_gb2312.py

PS C:\Users\soga\Desktop> python .\test_utf8.py
寮
PS C:\Users\soga\Desktop> python .\test_gb2312.py
張

造成兩個文件運行結果不同的原因是系統終端與字符串編碼不一致。Python會試圖把內部編碼的字符串轉化成當前執行程序的終端環境下所使用的編碼方式(sys.stdout.encoding),調用print(sys.stdout.write)後輸出。前面提到中文Windows下,cmd的環境編碼爲CP936cmd以自己的編碼打印UTF-8內容,必然會造成錯誤。Python可以通過locale查看運行環境的編碼,一般情況下,cmd的編碼與locale一致。

import locale

print(locale.getdefaultlocale())

Windows下可以通過chcp修改cmd的終端編碼。修改終端編碼爲CP2093,運行example.py查看變化,此時Python的sys.output.encoding與終端編碼保持一致,系統環境編碼保持不變。

# example.py
import locale
import sys

print (locale.getdefaultlocale())
print (sys.stdout.encoding)

# cmd.exe
PS C:\Users\soga\Desktop> chcp 20936
Active code page: 20936

PS C:\Users\soga\Desktop> python2 .\example.py
('zh_CN', 'cp936')
'cp20936'

爲什麼要在文件開頭加入# -- coding: UTF-8 --`*

PEP263解釋了編碼註釋的來源

This PEP proposes to introduce a syntax to declare the encoding of a python source file. The encoding information is then used by the python parser to interpret the file using the given encoding.

Python解析器使用給定的編碼來解釋文件,Python的默認解釋編碼爲ASCII,因此不添加UTF-8或CP936聲明,Python解釋器在遇到中文時是無法翻譯源代碼的。

Python系統編碼
查看Python系統編碼,默認爲ascii

import sys

print (sys.getdefaultencoding())    # 'ascii'

Python2.X中的字符串

  • str:是字節串(container for bytes),由 Unicode 經過編碼(encode)後的字節組成的。
  • unicode:真正意義上的字符串,其中的每個字符用 Unicode 中對應的 Code Point 表示。

x_str由兩個16進制數\xd5\xc5表示,看起來很眼熟,跟我們前面展示的test_gb2312內容一樣。說明此時Python使用CP936編碼中文字符。x_utf的\u5f20則是unicode中字符""的的代碼點。

>>> x_str = '張'
>>> x_utf = u'張'
>>> x_str
'\xd5\xc5'
>>> x_utf
u'\u5f20'
>>> x_gb2312 = x_utf.encode('gb2312') # 與x_str相同
>>> x_gb2312
'\xd5\xc5'

總結,str類型就是unicode經過編碼後的16進制字節數組。

unicode與str的轉換

Python爲兩種類型的字符串提供了兩個互相轉換的方法。

S.encode([encoding[,errors]]) -> object
S.decode([encoding[,errors]]) -> object

上圖中綠色線段標示的即爲我們常用的轉換方法,紅色標示的轉換在 python 2.x 中是合法的,不過沒有什麼意義,通常會拋出錯誤(可以參見What is the difference between encode/decode?)。下面是兩種類型之間的轉換示例:

zh_str = '中文'
zh_dec = zh_str.decode('gb2312')
print("%s %s %s %s" % (type(zh_str), '->', type(zh_dec), repr(zh_dec)))

zh_enc = zh_dec.encode('gb18030')
print("%s %s %s %s" % (type(zh_dec), '->', type(zh_enc), repr(zh_enc)))

# idle output
<type 'str'> -> <type 'unicode'> u'\u4e2d\u6587'
<type 'unicode'> -> <type 'str'> '\xd6\xd0\xce\xc4'

Python系統編碼引起的異常

隱藏的解碼

str和unicode類型都可以用來表示字符串,爲了方便它們之間進行操作,Python並不要求在操作之前統一類型,所以下面的代碼是合法的,並且能得到正確的輸出:

>>> s = u'hello ' + 'John'
>>> s, type(s)
(u'hello John', <type 'unicode'>)

因爲str類型是隱含有某種編碼方式的字節碼,所以Python內部將其解碼爲unicode後,再和unicode類型進行"+"操作,最後返回的結果也是unicode類型。第2步的解碼過程是在幕後悄悄發生的,默認採用ascii來進行解碼,可以通過 sys.getdefaultencoding() 來獲取默認編碼方式。Python之所以採用 ascii,是因爲 ascii 是最早的編碼方式,是許多編碼方式的子集。不過正是這個不可見的解碼過程,有時候會導致出乎意料的解碼錯誤,考慮下面的代碼:

>>> s = u'hello ' + '約翰'

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    s = u'hello ' + '約翰'
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd4 in position 0: ordinal not in range(128)

上面在字符串的"+"操作時,Python偷偷對"約翰"用ascii做解碼操作,所以拋出了UnicodeDecodeError異常。其實上面操作等同於u'hello ' + '約翰'.decode('ascii')

隱藏的編碼

與編碼類似,在沒有人爲指定編碼的情況下,Python也會偷偷的使用ascii對字符串編碼。嘗試直接解碼str類型的字符串,一般情況下,會正確的解碼爲unicode字符串。

>>> s = 'John'.decode()
>>> s
u'John'

換成中文試試

>>> s = '約翰'.decode()

Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    s = '約翰'.decode()
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd4 in position 0: ordinal not in range(128)

上面的錯誤提示信息'ascii' codec can't decode...已經很明顯的告訴我們,Python默認使用ascii對字符串解碼,正確的做法是指定合適的類型,比如'約翰'.decode('gb2312')

解決編碼的常見方法

  • 修改Python系統編碼

在程序開頭加上以下三行代碼。實際使用不推薦這種方法,網上的評論顯示其會產生bug,查看StackOverflow的討論

import sys

reload(sys) 
sys.setdefaultencoding('utf-8')

特別指出一點,sys中的setdefaultencoding()在啓動後就被刪除,在導入sys後是找不到該方法的。刪除代碼的位置在/Lib/site.py下的main函數

def main():
    ...
    if hasattr(sys, "setdefaultencoding"):
        del sys.setdefaultencoding
  • 人爲指定編碼
    輸入時,將外部文件或信息流轉換成unicode處理。輸出時根據外部的要求轉化爲相應的編碼,儘量在程序內部統一使用unicode類型

參考內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章