最近在用python寫多語言的一個插件時,涉及到python3.x中的unicode和編碼操作,本文就是針對編碼問題研究的匯總,目前已開源至github。以下內容來自項目中的README。
1 ASCII、UNICODE、GBK、CP936、MSCS
1.1 ASCII
美國信息交換標准碼。 在計算機的存儲單元中,一個ASCII碼值占一個字節(8個二進制位),但其最高位(b7)用作奇偶校驗位。ASCII(American Standard Code for Information Interchange),是一種單字節的編碼。計算機世界里一開始只有英文,而單字節可以表示256個不同的字符,可以表示所有的英文字符和許多的控制符號。不過ASCII只用到了其中的一半(\x80以下),這也是MBCS得以實現的基礎。
1.2 ISO8859-1、EASCII
EASCII是ASCII的擴充,把第八位也用來存儲信息;在Windows中用Alt+小鍵盤數字輸入的就是EASCII碼對應字符。ISO8859-1就是EASCII最典型的實現,基本能夠覆蓋西歐的拉丁字母,所以又叫Latin-1。有些國外程序就要求使用ISO8859-1編碼以保證Binary Safe,比如著名的XMB。
1.3 Unicode、UTF-8
Unicode是業界的一種標准,它可以使電腦得以呈現世界上數十種文字的系統。 后來,有人開始覺得太多編碼導致世界變得過於復雜了,讓人腦袋疼,於是大家坐在一起拍腦袋想出來一個方法:所有語言的字符都用同一種字符集來表示,這就是Unicode。
最初的Unicode標准UCS-2使用兩個字節表示一個字符,所以你常常可以聽到Unicode使用兩個字節表示一個字符的說法。但過了不久有人覺得256*256太少了,還是不夠用,於是出現了UCS-4標准,它使用4個字節表示一個字符,不過我們用的最多的仍然是UCS-2。
UCS(Unicode Character Set)還僅僅是字符對應碼位的一張表而已,比如"漢"這個字的碼位是6C49。字符具體如何傳輸和儲存則是由UTF(UCS Transformation Format)來負責。
一開始這事很簡單,直接使用UCS的碼位來保存,這就是UTF-16,比如,"漢"直接使用\x6C\x49保存(UTF-16-BE),或是倒過來使用\x49\x6C保存(UTF-16-LE)。但用着用着美國人覺得自己吃了大虧,以前英文字母只需要一個字節就能保存了,現在大鍋飯一吃變成了兩個字節,空間消耗大了一倍……於是UTF-8橫空出世。
UTF-8是一種很別扭的編碼,具體表現在他是變長的,並且兼容ASCII,ASCII字符使用1字節表示。然而這里省了的必定是從別的地方摳出來的,你肯定也聽說過UTF-8里中文字符使用3個字節來保存吧?4個字節保存的字符更是在淚奔……(具體UCS-2是怎么變成UTF-8的請自行搜索) Unicode的實現方式不同於編碼方式,一個字符的Unicode編碼是確定的,但是在實際傳輸過程中,由於不同系統平台的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。於是就有了UTF-8、UTF-16、UTF-32。
UTF-8使用一至四個字節為每個字符編碼:
ASCII字符只需一個字節編碼(Unicode范圍由U+0000至U+007F)。帶有附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母(即以ISO 8859為主的)則需要二個字節編碼(Unicode范圍由U+0080至U+07FF)。其他基本多文種平面(BMP)中的字符(這包含了大部分常用字,包括漢字)使用三個字節編碼。其他極少使用的Unicode 輔助平面的字符使用四字節編碼。它唯一的好處在於兼容ASCII。 UTF-16則是以U+10000為分界線,使用兩個字節或者四個字節存儲。 UTF-32則是全部使用4字節編碼,很浪費空間。
1.4 GB2312、GBK、GB18030
GB是中國荒謬的國家標准。GB2312、GBK、GB18030各為前一個的擴展。
我從來討厭GB編碼,因為它毫無國際兼容性。更荒謬的是,GBK和GB18030幾乎是照着Unicode字符集選取的字庫。這樣多此一舉地弄出一套編碼,還強制所有在中國銷售的操作系統必須使用它,真是天朝特色。
另外,對於GB編碼PHP是不認賬的,mb_detect_encoding函數會把GB編碼識別成CP936。
1.5 MSCS
然而計算機世界里很快就有了其他語言,單字節的ASCII已無法滿足需求。后來每個語言就制定了一套自己的編碼,由於單字節能表示的字符太少,而且同時也需要與ASCII編碼保持兼容,所以這些編碼紛紛使用了多字節來表示字符,如GBxxx、BIGxxx等等,他們的規則是,如果第一個字節是\x80以下,則仍然表示ASCII字符;而如果是\x80以上,則跟下一個字節一起(共兩個字節)表示一個字符,然后跳過下一個字節,繼續往下判斷。
這里,IBM發明了一個叫Code Page的概念,將這些編碼都收入囊中並分配頁碼,GBK是第936頁,也就是CP936。所以,也可以使用CP936表示GBK。
MBCS(Multi-Byte Character Set)是這些編碼的統稱。目前為止大家都是用了雙字節,所以有時候也叫做DBCS(Double-Byte Character Set)。必須明確的是,MBCS並不是某一種特定的編碼,Windows里根據你設定的區域不同,MBCS指代不同的編碼,而Linux里無法使用MBCS作為編碼。在Windows中你看不到MBCS這幾個字符,因為微軟為了更加洋氣,使用了ANSI來嚇唬人,記事本的另存為對話框里編碼ANSI就是MBCS。同時,在簡體中文Windows默認的區域設定里,指代GBK。
2 open函數
open狀態rb對應的是_io.BufferedReader,r對應的是_io.TextIOWrapper
class io.TextIOWrapper(buffer, encoding=None, errors=None, newline=None,
line_buffering=False)
A buffered text stream over a BufferedIOBase binary stream. It inherits TextIOBase. encoding gives the name of the encoding that the stream will be decoded or encoded with. It defaults to locale.getpreferredencoding().
3 encode和decode方法
字符串在Python內部的表示是unicode編碼,因此,在做編碼轉換時,通常需要以unicode作為中間編碼,即先將其他編碼的字符串解碼(decode)成unicode,再從unicode編碼(encode)成另一種編碼decode的作用是將其他編碼的字符串轉換成unicode編碼,如str1.decode('gb2312'),表示將gb2312編碼的字符串str1轉換成unicode編碼。 encode的作用是將unicode編碼轉換成其他編碼的字符串,如str2.encode('gb2312'),表示將unicode編碼的字符串str2轉換成gb2312編碼。
因此,轉碼的時候一定要先搞明白,字符串str是什么編碼,然后decode成unicode,然后再encode成其他編碼。代碼中字符串的默認編碼與代碼文件本身的編碼一致。
如:s='中文' 如果是在utf8的文件中,該字符串就是utf8編碼,如果是在gb2312的文件中,則其編碼為gb2312。這 種情況下,要進行編碼轉換,都需要先用decode方法將其轉換成unicode編碼,再使用encode方法將 其轉換成其他編碼。通常,在沒有指定特定的編碼方式時,都是使用的系統默認編碼創建的代碼文件
4 相關代碼
python默認編碼
default encodings in Python are:
Python 2.x: ASCII
Python 3.x: UTF-8
win7中文環境中對應的系統參數
print('<strong>python系統參數:')
print(locale.getdefaultlocale()) #('zh_CN', 'cp936')
print(locale.getpreferredencoding()) # cp936
print(sys.getdefaultencoding()) #utf-8
print(sys.getfilesystemencoding())#mbcs
print(sys.maxunicode)
print(codecs.lookup('utf-8'))#codeinfo class
('zh_CN', 'cp936')
cp936
utf-8
mbcs
1114111
utf-8, gbk codecs error
ch_str = '中文'
try:
codecs_decode(codecs_encode(ch_str,'gbk'))
except Exception:
print('<strong>utf-8 codec decode error')
codecs_decode(codecs_encode('1ère Recuérdame écouteur ça'))
codecs_decode(codecs_encode('1ère Recuérdame écouteur ça'),'gbk')
try:
codecs_decode(codecs_encode('1ère','gbk'))
except Exception:
print('<strong>utf-8 codec decode error')
code_str = '中國'
print(code_str.encode().decode())
print(code_str.encode().decode('mbcs','ignore'))
try:
print(code_str.encode().decode('gbk','strict'))
except Exception:
print('<strong>gbk codec decode error')
binary寫文件
#write french in file
def write_file(filename):
with open(filename,'wb') as file:
file.write('ry dialect: /a/, /ɑ/, /e/, /ɛ/, /ə/, /i/, /o/, /ɔ/, /'.encode())
def write_file_append(filename,string):
line_list = []
with open(filename,'rb') as file:
for line in file:
line_list.append(line)
with open(filename,'wb') as file:
for i in range(len(line_list)):
file.write(line_list[i])
file.write(string.encode())
5 參考資料
-
python unicode howto:(unicode codepoints): http://docs.python.org/3/howto/unicode.html
-
python unicode&encoding: http://docs.python.org/3.3/library/codecs.html#encodings-and-unicode
-
unicode further reading : http://www.diveinto.org/python3/strings.html#py-encoding
-
new in the python3.0: http://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit
-
codecs test: http://pymotw.com/2/codecs/
-
py33 file (locale.getpreferredencoding()): http://www.diveinto.org/python3/files.html
-
py33 io (buffering): http://docs.python.org/3.1/library/io.html#io.TextIOWrapper
-
py33 shutil(high-level operations on files): http://docs.python.org/3.3/library/shutil.html
-
codecs源碼:(C:\Python33\Lib\codecs.py)
PEP and ISSUES:
- ISSUES:
distutils.commands.bdist_wininst.bdist_wininst.get_inidata use mdcs encodinghttp://bugs.python.org/issue10945
bytes.decode('mbcs', 'ignore') does replace undecodable bytes on Windows Vista or laterhttp://bugs.python.org/issue12281
- PEP393:
Flexible String Representation http://www.python.org/dev/peps/pep-0393/#discussion
PEP0263: Defining Python Source Code Encodings http://www.python.org/dev/peps/pep-0263/
p.s.
- sublime3/2的錯誤編碼
(由於sublime自身的編碼原因,所以可能造成在ctrl+b編譯的過程中,對多語言的編碼出現錯誤)
