python数字编码_Python 编码为什么那么蛋疼?
說到底,覺得蛋疼只是因為自己編碼知識沒學好。
這里一些答案說的不錯,但這種問題光從Python2本身的角度去回答意義并不大。如果沒有理解字符編碼的模型與原理,很難說以后換個語言換個環境就不會重蹈覆轍。進程、線程、Socket這些東西在各個語言中形式各異,但核心模型與原理都一樣。字符編碼亦然,它的核心知識并不是和Python綁定的。這里把以前的筆記貼在這里,供后人鑒之。
======Update
沒耐心看基礎的可以直接拉到最后,直接看Python2相關編碼問題的解釋。
現代編碼模型Tags:字符集
Abstract: 字符編碼,在計算機導論中經常作為開門的前幾個話題來講,然而很多CS教材對這個話題基本都是走馬觀花地幾頁帶過。導致了許多人對如此重要且基本的概念認識模糊不清。直到在實際編程中,尤其是遇到多語言、國際化的問題,被虐的死去活來之后才痛下決心去重新鉆研。諸如此類極其基礎卻又容易被人忽視的的知識點還有:大小端表示,浮點數細節,正則表達式,日期時間處理等。本文是系列的第一篇,旨在闡明字符編碼這個大坑中許多糾纏不清的概念。
基本概念
現代編碼模型自底向上分為五個層次:
抽象字符表 ACR (Abstract Character Repertoire)
編碼字符集 CCS (Coded Character Set)
字符編碼表 CEF (Character Encoding Form)
字符編碼方案 CES (Character Encoding Schema)
傳輸編碼語法 TES (Transfer Encoding Syntax)
抽象字符集 ACR
抽象字符集是現代編碼模型的最底層,它是一個集合,通過枚舉指明了所屬的所有抽象字符。但是要了解抽象字符集是什么,我們首先需要了解什么是**字符**與**抽象字符**
字符 (character, char)字符是指字母、數字、標點、表意文字(如漢字)、符號、或者其他文本形式的書寫“原子”。
例: `a`,`啊`,`あ`,` α`,`Д`等,都是抽象的字符。抽象字符(Abstract Character)抽象字符就是抽象的字符。
像`a`這樣的字符是有形的,但在計算機中,有許多的字符是空白的,甚至是不可打印的。比如ASCII字符集中的NULL,就是一個抽象字符。
注意\x00,\000,NULL,0 這些寫法都只是這個抽象字符的某種表現形式,而不是這個抽象字符本身。
抽象字符集 ACR (Abstract Character Repertoire)抽象字符集顧名思義,指的是**抽象字符的集合**。
已經有了很多標準的字符集定義: Character Sets
比如US-ASCII, UCS(Unicode), GBK這些我們耳熟能詳的名字,都是(或者至少是)抽象字符集。
US-ASCII定義了128個抽象字符的集合。GBK挑選了兩萬多個中日韓漢字和其他一些字符組成字符集,而UCS則嘗試去容納一切的抽象字符。它們都是抽象字符集。
抽象字符 英文字母`A`同時屬于US-ASCII, UCS, GBK這三個字符集。
抽象字符 中文文字`蛤`不屬于US-ASCII,屬于GBK字符集,也屬于UCS字符集。
抽象文字 Emoji ` `不屬于US-ASCII與GBK字符集,但屬于UCS字符集。
集合的一個重要特性,就是無序性。
集合中的元素都是無序的,所以抽象字符集中的字符都是**無序的**。
抽象字符集對應的是python中的set的概念。
例:我可以自己定義一個字符的集合,叫這個集合為haha字符集。
haha_acr = { 'a', '吼', 'あ', ' α', 'Д' }
大家覺得抽象字符集這個名字太啰嗦,所以有時候直接叫它字符集。
最后需要注意一點的是,抽象字符集也是有開放與封閉之分的。
ASCII抽象字符集定義了128個抽象字符,再也不會增加。這是一個封閉字符集。
Unicode嘗試收納所有的字符,一直在不斷地擴張之中。最近(2016.06)Unicode9.0.0已經收納了128,237個字符,并且未來仍然會繼續增長,這是一個開放的字符集。
編碼字符集 CCS (Coded Character Set)Coded Character Set. A character set in which each character is assigned a numeric code point. Frequently abbreviated as character set, charset, or code set; the acronym CCS is also used.編碼字符集是一個每個所屬字符都分配了碼位的抽象字符集。
編碼字符集(CCS)也經常簡單叫做字符集(Character Set)。這樣的叫法經常會將抽象字符集ACR與編碼字符集CCS搞混。不過大多時候人們也不在乎這種事情。
抽象字符集是抽象字符的集合,而集合是無序的。
無序的抽象字符集并沒有什么卵用,因為我們只能判斷某個字符是否屬于某個字符集,卻無法方便地引用,指稱這個集合中的某個特定元素。
以下兩個表述指稱了同一個字符,但哪一種更方便呢?
ASCII(抽象)字符集中的那個代表什么都沒有的通常表示為NULL的抽象字符
ASCII(編碼)字符集中的0號字符為了更好的描述,操作字符,我們可以為抽象字符集中的每個字符關聯一個數字編號,這個數字編號稱之為碼位(Code Point)。
通常根據習慣,我們為字符分配的碼位通常都是非負整數,習慣上用十六進制表示。且一個編碼字符集中字符與碼位的映射是一一映射。
舉個例子,為haha抽象字符集進行編碼,就可以得到haha編碼字符集。
haha_ccs = { 'a' : 0x0, '吼':0x1 , 'あ':0x2 , ' α':0x3 , 'Д':0x4 }字符`吼`與碼位`0x1`關聯,這時候,在haha編碼字符集中,`吼`就不再是一個單純的抽象字符了,而是一個編碼字符(Coded Chacter),且擁有碼位0x1。
如果說抽象字符集是一個Set,那么編碼字符集就可以類比為一個Dict。
CCS = { k:i for i, k in enumerate(ACR)}它的key是字符,而value則是碼位。至于碼位具體是怎樣分配的,這個規律就不好說了。比如為什么我想給haha_ccs的`吼`字符分配碼位`0x1`而不是`0x23333`呢?因為這樣能續一秒,反映了CCS設計者的主觀趣味。
編碼字符集有許許多多,但最出名的應該就是US-ASCII和UCS了。ASCII因為太有名了,所以就不說了。
統一字符集 UCS (Universal Character Set)最常見的編碼字符集就是統一字符集 UCS
UCS. Acronym for Universal Character Set, which is specified by International Standard ISO/IEC 10646, which is equivalent in repertoire to the Unicode Standard.
UCS就是統一字符集,就是由 ISO/IEC 10646所定義的編碼字符集。通常說的“Unicode字符集”指的就是它。不過需要辨明的一點是,“Unicode”這個詞本身指的是一系列用于計算機表示所有語言字符的標準。
基本上所有能在其他字符集中遇到的符號,都可以在UCS中找到,而一些新的不屬于任何傳統字符集的字符,例如Emoji,也會收錄于UCS中。這也是UCS地位超然的原因。
舉個例子,UCS中碼位為0x4E00~0x9FFF的碼位,就用于表示“中日韓統一表意文字”
大家喜聞樂見的Emoji表情則位于更高的碼位,例如“哭笑”在UCS中的碼位就是0x1F602。
(如果這個站點不支持Emoji,你就看不到這個字符了,上面那個是圖片…)
>>> ' '.decode('utf-8')
u'\U0001f602'關于CCS,這些介紹大抵足夠了。
不過還有一個細節需要注意。按照目前最新Unicode 9.0.0的標準,UCS理論上收錄了128,237個字符,也就是0x1F4ED個。不過如果進行一些嘗試會發現,實際能用的最大的碼位點在0x1F6D0 ,也就是128,720,竟然超過了收錄的字符數,這又是為什么呢?
碼位是非負整數沒錯,但這不代表它一定是連續分配的。
出現這種情況只有一個原因,那就是UCS的碼位分配不是連續的,中間有一段空洞,即存在一段碼位,沒有分配對應的字符。
實際上,UCS實際分配的碼位是 0x0000~0x0xD7FF與 0xE000~0x10FFFF這兩段。中間0xD800~0xDFFF這2048個碼位留作它用,并不對應實際的字符。如果直接嘗試去輸出這個碼位段的'字符',結果會告訴你這是個非法字符。例如在python2中嘗試打印碼位0xDDDD的字符:
>>> print u'\UDDDD'
File "", line 1
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-5: truncated \UXXXXXXXX escape0x0000~0xD7FF | 0xE000~0x10FFFF 稱為Unicode標量值(Unicode scala value)
0xD800~0xDBFF 稱為High-surrogate
0xDC00~0xDFFF 稱為Low-surrogateUnicode標量值就是實際存在對應字符的碼位。
為什么中間一端的碼位會留空,則是為了方便下一個層次的字符編碼表CEF的UTF-16而處理的。
其他編碼字符集除了ASCII與UCS,世界上還有許許多多的字符集。
在US-ASCII誕生與Unicode誕生之間,很多英語之外的字符無法在計算機中表示。
大家八仙過海各顯神通,定義了許許多多其他的字符集。
例如GBK字符集,以及其近似實現 Code Page 936。
這些字符集中的字符,最后都匯入了Unicode中。
字符編碼表 CEF (Character Encoding Form)Unicode Encoding Form. A character encoding form that assigns each Unicode scalar value to a unique code unit sequence. The Unicode Standard defines three Unicode encoding forms: UTF-8, UTF-16, and UTF-32
現在我們擁有一個編碼字符集了,Let's say: UCS。
這個字符集中的每個字符都有一個非負整數碼位與之一一對應。
看上去很好,既然計算機可以存儲整數,而現在字符已經能表示為整數,我們是不是可以說,用計算機存儲字符的問題已經得到了解決呢?
慢!還有一個問題沒有解決。
在講抽象字符集ACR的時候曾經提起,UCS是一個開放字符集,未來可能有更多的符號加入到這個字符集中來。也就是說UCS需要的碼位,理論上是無限的。
但計算機整形能表示的整數范圍是有限的。譬如,一個字節的無符號單字節整形(unsigned char, uint8)能夠表示的碼位只有0~0xFF,共256個;一個無符號短整形(unsigned short, uint16)的可用碼位只有0~0xFFFF,共65536個;而一個標準整形(unsigned int, uint32)能表示的碼位只有0~0xFFFFFFFF,共4294967296個。
雖然就目前來看,UCS收錄的符號總共也就十多萬個,用一個uint可以表示幾十億個字符呢。但誰知道哪天制定Unicode標準的同志們不會玩心大發造幾十億個Emoji加入UCS中。所以說到底,一對有限與無限的矛盾,必須通過一種方式進行調和。這個解決方案,就是字符編碼表(Character Encoding Form)。
字符編碼表將碼位(Code Point)映射為碼元序列(Code Unit Sequences)。對于Unicode而言,字符編碼表將Unicode標量值(Unicode scalar value)一一映射為碼元序列(Code Unit Sequences)。
碼元Code unit: The minimal bit combination that can represent a unit of encoded text for processing or interchange.碼元是能用于處理或交換編碼文本的最小比特組合。通常計算機處理字符的碼元為一字節,即8bit。同時因為計算機中char其實是一種整形,而整形的計算往往以計算機的字長作為一個基礎單元,通常來講,也就是4字節。
Unicode定義了三種不同的CEF,分別采用了1字節,2字節,4字節的碼元,正好對應了計算機中最常見的三種整形長度:
在Unicode中,指定了三種標準的字符編碼表,UTF-8, UTF-16, UTF-32。分別將Unicode標量值映射為比特數為8、16、32的碼元的序列。UTF-8的碼元為uint8, UTF-16的碼元為uint16, UTF-32的碼元為uint32。
當然也有一些非標準的CEF,如UCS-2,UCS-4,在此不多介紹。
需要注意一點的是,CEF將碼位映射為碼元序列。這個映射必須是一一映射(雙射)。
因為當使用CEF進行編碼(Encode)時,是將碼位映射為碼元序列。
而當使用CEF進行解碼(Decode)時,是將碼元序列還原為碼位。
為了保證兩個過程都不出現歧義,必須保證CEF是一個雙射。
知道了字符編碼表CEF是什么還不夠,我們還需要知道它是怎么做的。
即:如何將一個無限大的整數,一一映射為指定字寬的碼元序列。
這個問題可以通過變長編碼來解決:無論是UTF-8還是UTF-16,本質思想都是通過預留標記位來指示碼元序列的長度,從而實現變長編碼。
各個CEF的細節我建議參看維基百科
Unicode 9.0.0)
舉個例子:
字符編碼方案 CES (Character Encoding Schema)Unicode encoding scheme: A specified byte serialization for a Unicode encoding
form, including the specification of the handling of a byte order mark (BOM), if
allowed.簡單說,字符編碼方案 CES等于 字符編碼表CEF加上字節序列化的方案。
通過字符編碼表CEF,我們已經可以將字符轉為碼元序列。無論是哪種UTF-X的碼元,都可以找到計算機中與之對應的整形存放。那么現在我們能說存儲處理交換字符這個問題解決了嗎?
還不行。
假設一個字符按照UTF16拆成了若干個碼元組成的碼元序列,因為每個碼元都是一個unsigned short,實際上是兩個字節。因此將碼元序列化為字節序列的時候,就會遇到一些問題。
大小端序問題:每個碼元究竟是高位字節在前還是低位字節在前呢?
字節序標記問題:另一個程序如何知道當文本是什么端序的呢?這些都是CEF需要操心的問題。
對于網絡交換和本地處理,大小端序各有優劣。這個問題不屬于本文范疇。
字節序標記BOM (Byte Order Mark),則是放置于編碼字節序列開始處的一段特殊字節序列,用于表示文本序列的大小端序。
對于這兩個問題的不同答案,在3種CEF:UTF-8,UTF-16,UTF-32上。
Unicode實際上定義了 7種 字符編碼方案CES:
UTF-8
UTF-16LE
UTF-16BE
UTF-16
UTF-32LE
UTF-32BE
UTF-32其中UTF-8因為已經采用字節作為碼元了,所以實際上不存在字節序的問題。其他兩種CES嘛,都有一個大端版本一個小端版本,還有一個隨機應變大小端帶BOM的版本。
下面給一個Python編碼的小例子,將Emoji:哭笑 轉換為各種CES。
這里也出現一個問題,歷史上字符編碼方案(Character Encoding Schema)曾經就是指UTF(Unicode Transformation Formats)。所以UTF-X到底是屬于字符編碼方案CES還是屬于字符編碼表CEF是一個模棱兩可的問題。UTF-X可以同時指代字符編碼表CEF或者字符編碼方案CES。
UTF-8問題還好,因為UTF-8的字節序列化方案太樸素了,以至于CES和CEF都沒什么區別。但其他兩種:UTF-16,UTF-32,就比較棘手了。當我們說UTF-16時,既可以指代UTF-16字符編碼表,又可以指代UTF-16字符編碼方案。所以當有人說“這個字符串是UTF-16編碼的”時,鬼知道他到底說的到底是一個(UTF-16 encoding form的)碼元序列還是(UTF-16 encoding schema 的)字節流。
簡單的說,字符編碼表CEF和字符編碼方案CES區別如下:
c ∈ CCS ---CEF--> Code Unit Sequence
c ∈ CCS ---CES--> Byte Sequence
字符編碼表CEF將碼位映射為碼元序列,而字符編碼方案CES將碼位序列化為字節流。
我們通常所說的動詞編碼(Encode)就是指使用CES,將CCS中字符組成的字符串轉變為字節序列。
而解碼(Decode)就是反過來,將 編碼字節序列通過CES的一一映射還原為CCS中字符的序列。
除了Unicode標準定義的七中CES,還有兩種CES: UCS-2,UCS-4 。嚴格來說,UCS-2和UCS-4屬于字符編碼表CEF的層次,不過鑒于其樸素的序列化方案,也可以理解為CES。這兩種CES的特點是采用定長編碼,比如UCS-2直接把碼位序列化為unsigned short。之前一直很流行,但當UCS中字符越來越多,超過65536個之后,UCS-2就GG了。至于UCS-4,基本和UTF-32差不多。雖說有生之年基本不可能看到UCS大小超出四字節的表示范圍,但每個字符統一用4字節來存儲這件事本身就很蠢了……。
當然除了UCS,其他字符集,例如US-ASCII,GBK,也會有自己的字符編碼方案,只不過我們很少聽說,一個很重要的原因是,這些字符集的編碼方案太簡單了,以至于CCS,CEF,CES三層直接合一了。
例如US-ASCII的CES,因為ASCII就128個字符,只要直接把其碼位轉換成(char),就完成了編碼。如此簡單的編碼,直接讓CCS,CEF,CES三層合一。很多其他的字符集也與之類似。
傳輸編碼語法(Transfer Encoding Syntax)通過CES,我們已經可以將一個字符表示為一個字節序列。
但是有時候,字節序列表示還不夠。比如在HTTP協議中,在URL里,一些字符是不允許出現的。這時候就需要再次對字節流進行編碼。
著名的Base64編碼,就是把字節流映射成了一個由64個安全字符組成字符集所表示的字符流。從而使字節流能夠安全地在Web中傳輸。
不過這一塊的內容已經離我們討論的主題太遠了。
Python2中的編碼問題回到正題,出現各種編碼問題,無非是哪里的編碼設置出錯了
常見編碼錯誤的原因有:
Python解釋器的默認編碼
Python源文件文件編碼
Terminal使用的編碼
操作系統的語言設置當然要我說,最嚴重的問題其實是:
Python2的默認編碼方案的非常不合理。
Python2的字符串類型與字符串字面值很容易讓人混淆。第一個問題:
Python2解釋器的默認編碼方案(CES)是US-ASCII (WTF!!!)。
Java,C#,Javascript等語言內部的默認編碼方案都是UTF-16,Go語言的內部默認編碼方案使用UTF-8。與之相比,默認使用US-ASCII的python2簡直是骨骼清奇,當然,這也有一部分歷史原因在里頭。Python3就乖乖地改成UTF-8了。
第二個問題:
python的默認'字符串類型'與其叫字符串,不如叫字節串,用下標去訪問的每一個元素都是一個字節。而類型才是真正意義上的字符串,用下標去訪問的每一個元素都是一個字符(雖然底下可能每個字符長度不同)。
字符串 與 字節串 的關系為:
字符串 通過 字符編碼方案(CES)編碼(Encode) 得到字節串
字節串 通過 字符編碼方案(CES)解碼(Decode) 得到字符串字節串就字節串,為啥要起個類型名叫str呢?另外,字面值語法用一對什么前綴都沒有的引號表示str,這樣的設計非常反直覺。
當然,與這樣的類型設計以及兩者的關系設計本身是無可厚非的。該黑的應該是這兩個類型起的名字和字面值表示方法。至于怎么改進是好的,Python3已經給出答案。
以前我曾用IPython Notebook記過一個筆記,可惜知乎的顯示效果太差了。只能弄成圖片貼上來了。
總結
以上是生活随笔為你收集整理的python数字编码_Python 编码为什么那么蛋疼?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python元编程_Python 元编程
- 下一篇: python自动控制生产线输送线_一个关