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