<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    海上月明

    editer by sun
    posts - 162, comments - 51, trackbacks - 0, articles - 8
       :: 首頁 :: 新隨筆 ::  :: 聚合  :: 管理

    [轉] 復雜的Unicode,疑惑的Python

    Posted on 2009-07-20 18:44 pts 閱讀(2638) 評論(1)  編輯  收藏 所屬分類: Python

    猛然間看到這篇文章,才發現原來自己理解的uniocde還是表面,這篇文章又說明了很多深層次的內容,值得一看。

    From:http://jawahh.sjtubbs.org/2008/07/unicodepython.html

    2008年7月21日

    復雜的Unicode,疑惑的Python

    Python 3000決定采用Unicode作為字符的默認編碼。這不是什么新聞了,也是國際化的大勢所趨。但實際上似乎沒有那么簡單。最近python-dev郵件列表吵的一個問題就很有意思。

    7月2日,一個叫Jeroen Ruigrok van der Werven的人以UCS2/UCS4 default 為 標題說了問題。Python雖然采用unicode作為默認字符,但語言內部用什么方法表示unicode字符串并沒有一致的規定。在編譯的時候可以選擇 用UCS2或者UCS4編碼。作者說,隨著unicode中的CJK象形字擴展B(CJK ideographs extension B)已經加入最新的unicode規范,擴展C已經在投票表決階段,擴展D已經在開發,使用UCS2編碼已經不能滿足預期未來的應用了。所以應該默認使用 UCS4編碼。而且作者認為允許UCS2和UCS4兩種編碼會產生編程一致性的問題。比如,在用UCS2編碼的python 3中:

    >>> len("\N{MUSICAL SYMBOL G CLEF}")
    2
    而在用USC4編碼中的python 3中:
    >>> len("\N{MUSICAL SYMBOL G CLEF}")
    1

    然后這個問題就引起了一場大混戰。這個問題到底是什么問題呢?到底為什么會允許那么奇怪的事情發生:同一個字符串在不同的編譯版本中長度不一樣呢?其實這個問題根植在unicode復雜的規范和歷史中。

    Unicode的一個中文名字叫“萬國碼”。這個翻譯很明確指出了Unicode的任務,為人類使用的文字都編一個號碼,解決他們在計算機中共存的 問題,消除計算機世界里面萬碼奔騰的兼容性問題。現在unicode 5.1規范已經于2008-04-04發布,而且也確實很大部分消除了計算機世界里面兼容問題,成為了事實規范。現在哪個稍大的新程序不支持 Unicode,是說不過去的。不過這不表示unicode里面沒有什么犄角旮旯影響著大家的使用。

    第一個問題是,unicode到底要給什么編號?對中文來說,當然是給字編號——在中文里面是這樣,但unicode并不是中文編碼,這個世界的書 寫文字五花八門,中文概念里面的字在其他書寫文字里面并不一定存在。在這里要澄清的兩個概念是glyph和character。Glyph是文字的圖像, 是我們書寫的最小單元,是屏幕上看到的,打印機上打出來的最小單元。Character是一種邏輯上和語言學上的描述,它并不完全等同于Glyph。 Glyph和Character之間有多種組合發生。一種情況是一個Glyph代表多個character。舉中文里面的多音字說明,比如“沒”字,它有 兩個讀音,而且意義完全不一樣,但只有一個表現形式。“沒”代表著一個glyph,代表著兩個character。如果再加上日語和韓語,這種同一個 glyph卻有多個character的情況就更多了。還有一個情況是多個character才能組成一個glyph。主要是一些字母的音調問題。中文里 面也有這個問題,比如“e”和“ˊ”這兩個character可以組成一個glyph“é”。還有一種我們不太熟悉的情況是多個glyph只是一個 character,主要出現在阿拉伯文中。在阿拉伯文中,一個字母在不同的書寫情況下可能有不同的表現形式(glyph)。還有一種情況 是,character沒有對應的glyph,比如我們常見的回車符。事實上,unicode并沒有給出一個標準的說法說明到底給什么編號,它走的是務實 主義。Unicode大致可以說的是給character編號,但也會照顧到各種語言的現實,會給glyph編號。對于編程,一個簡單的計算字符串長度就 會發生歧義。到底我們是計算unicode字符串的character數量還是glyph數量?在ascii編碼中沒有這個問題,因為它的 character和glyph是統一的。Unicode解決這個問題的方法是不僅僅給character編號,還給每個character編訂了 unicode character property。軟件可以計算character數量,也可以根據character查詢屬性,用于計算和顯示glyph。

    第二個問題是,unicode到底打算使用多少個編號?現在的Unicode使用了21bit的數字去編號。目前看來21bit在可預見的將來是足 夠使用的——除非人類發現了外星人文明,需要為他們編號。現在的unicode編號從0x0-0x10FFFF分為17個Plane,編號從0-16。從 0x0-0xFFFD為BMP(Basic Multilingual Plane),也就是前16bit,集中了大多現代書寫系統;從0x10000-0x1FFFD為SMP(Supplementary Mulitilingual Plane),包括了大多在歷史上曾經使用的書寫系統;從0x20000-0x2FFFD為SIP(Supplementary Ideographic Plane),用于每年新增加的象形文字;然后11個plane尚未使用;Plane 14(0xE0000-0xEFFFD)為SSP(Supplementary Special-Purpose Plane),存放一些爭議性比較大的字符(語義上比較模糊或者會給文字處理帶來麻煩),使用這些字符都需要多加小心;后面兩個Plane 15(0xF0000-0xFFFFD)和Plane 16(0x100000-0x10FFFD)為保留區,任何人都可以私自定義這個區域,當然Unicode規范也不保證這些區域可以在異構系統上順利交 換。還有一個特殊字符編號0xFEFF是BOM(Byte Order Mark),包括它對判斷Byte Order有特殊用途,所以它的另外一面0xFFFE也就被規定為非Unicode字符(也就是為什么Plane的結束都是0xFFFD的原因)。上面的 說法看起來沒有什么太大的問題,但這不是故事的全部。最開始的Unicode只打算用16bit的數字,也就是現在的BMP去實現它的目的,這個跟當年的 兼容和效率考量有關。但這顯然是不夠的,尤其對于龐大的CJK象形文字——至今Unicode已經包含了7萬多個CJK象形文字。這是個不幸的歷史。所以 早期的Unicode實現中,并沒有考慮到16bit以外的問題。比如大量使用的Windows和Java構建的系統。Unix系統倒是塞翁失馬焉知非 福,對Unicode的支持比較晚,所以大多都是用32bit去表示21bit的unicode編號。歷史的悲劇就這么產生了,雖然都是Unicode, 但歷史遺留系統和現代系統的不同表示還是給所有希望實現Portability的應用帶來尷尬的處境。Python的UCS2/UCS4問題就是其中之 一,但這不是造成這個問題的全部原因,還有下一個原因。

    第三個問題是,unicode到底是什么?這是個很嚴肅的問題。Unicode只是字符編號,字符編號的屬性,以及相關說明的集合。Unicode 不是平常所說的編碼(Encoding)。Unicode規范只是規定了每個字符的編號(Code Point)。雖然它是為計算機設計的規范,但這個編號和計算機如何存儲,如何表示這些字符沒有直接關系。在這一層,叫做CCS(Coded Character Set)。理論上如何表示Unicode字符是應用程序的自由,喜歡怎么表示就怎么表示,只要你的表示方法能找到字符對應的Code Point就行。當然,大家不能讓這種混亂出現,所以有了CEF(Character Encoding Form)這一層。這一層關注的是在計算機理論上如何從數字到映射Code Point,也就是如何在8bit為單位的計算機系統中表示21bit的Unicode Code Point。其中UCS2,UCS4,UTF32,UTF16,UTF8等等都是實際上使用的方案。理論上映射和實踐上映射不完全一樣,實踐上還要考慮異 構系統的可交換特性,也就是解決大小端問題的CES(Character Encoding Scheme)。所以又會有UTF32-LE,UTF32-BE,UTF16-BE,UTF16-LE,UTF8(對的,UTF8是CEF,也是 CES)。還有最后一層,是TES(Transfer Encoding Syntax)。這一層解決的問題是在特定傳輸環境中的編碼問題,比如把UTF8字符串再用base64編碼用于電子郵件傳輸。跟Python有關的是 CEF這一層。前面說過,歷史上Unicode的code point是16bit的,所以無論是UCS2,UCS4,UTF32,UTF16,UTF8都可以相安無事。對于前四者來說,都是一個code unit對應一個code point(code unit是CEF的最小單位,對于UCS4和UTF32是32bit,對于UCS2和UTF16是16bit,對于UTF8是8bit);對于UTF8來 說是1到3個code unit對應一個code point。這時候的UCS4和UTF32是等價的,UCS2和UTF16是等價的。后來unicode擴展為21位了。對于UCS4和UTF32來說, 還是一個code unit對應一個code point,對應用程序來說變化不大。而對于UTF8來說,變成了1-4個code unit對應一個code point(為什么是擴展到21bit這么奇怪的數字呢?我猜測是為了UTF8的效率,因為UTF8中4個code unit正好最多可以表示21bit的code point),因為UTF8本來就是長度可變的編碼,問題也不大。但對UCS2和UTF16來說,問題就比較頭疼了。UCS2和UTF16本來是固定長度 編碼,但現在無論如何也不可能用16bit的存儲表示21bit的code point。UCS2和UTF16在這里就分道揚鑣了。UCS2的處理方法很暴力,只保留低16位信息,忽略高5位的信息,也就是只兼容BMP中的 code point。而UTF16就變成了可變長度編碼。解決方法是在BMP中劃分出兩個保留區域,分別是0xD800-0xDBFF的High Surrogate Area和0xDC00-0xDFFF的Low Surrogate Area。編碼方案是,假如有一個大于0xFFFF的code point是X,那么讓Y=X-0x10000;Y顯然是介于0x00和0xFFFFF之間的20bit數據(這也就是為什么unicode雖然擴展到 21bit,但只有17個plane——理論上21bit可以表示32個plane)。假如Y這個數字的分隔為高10bit和低10bit(假如是 xxxxxxxxxxyyyyyyyyyy),那么X的UTF16編碼110110xxxxxxxxxx 110111yyyyyyyyyy,正好落在Surrogate Area里面。就這樣UTF16變成了長度可變編碼。用Python表示就是:

    high = ((X-0x10000)>>10)&0x3FF+0xD800
    low = (X-0x10000)&0x3FF+0xDC00

    但對使用UTF16編碼的程序來說,一個字符串的code point數量(也就是unicode character的數量)和code unit的數量不再是恒等的——如果代碼里面曾經簡單的恒等這兩個數量,代碼就出錯了。

    回到Python的問題上,由于歷史原因,現有的系統即有使用UCS2(Windows,Java——Java從1.5開始改為支持UTF16)也 有使用UCS4(Unix/Linux)的。為了在各個系統上的最大兼容性,Python的Unicode字符串在內存中的表示方式一直都有兩種,在編譯 的時候指定(有--with-wide-unicode的時候用UCS4)。對于同一個不在BMP范圍內的字符"\N{MUSICAL SYMBOL G CLEF}",UCS4的Python內部表示成一個UCS4 code unit,計算長度的時候自然就是等于一,因為code unit的數量和code point的數量是恒等的;但UCS2的Python為了不丟失信息,首先用UTF16的編碼方式把不在BMP范圍內的字符編碼成兩個UTF16 code unit,但計算長度的時候,返回的是code unit的數量,而不是code point的數量!所以郵件列表上有人說,UCS2的Python用UTF16的編碼處理了字符輸入,又按照UCS2的方式在內部使用。UCS2和 UTF16之間的混淆不清大概就是這個問題的根源。

    平心而論,UTF16并不是一個糟糕的編碼。它的優點是對于大多常用的字符(在BMP范圍內的)更緊湊,無論是分割字符,計算字符,都和UTF32 一樣。但問題就在于不是BMP范圍內的字符。要以code point為單位處理這些字符的UTF16編碼需要跟復雜和低效的算法,比如隨機訪問字符串中的某個字符從O(1)變成了O(N)。Java也是有這個問 題的語言,它曾經是UCS2,但從1.5開始,增加了一套處理UTF16字符的API(String.codePointCount / String.codePointAt / String.codePointBefore / String.offsetByCodepoints)。所以在Java中,原來的代碼會保持原來的UCS2處理方式,當你要使用超過BMP范圍的字符 時,可以使用新的API處理;這樣在兼容和正確處理之間找一個妥協方案。但Python由于用了兩種內部表示方案,問題就變得更復雜。程序員不僅要注意到 code unit和code point的區別,還要注意到UCS4和UCS2中的code unit區別。

    郵件列表中爭吵到最后的結果大概是在文檔中增加對這些區別的說明,同時增加一套新的API用于按照code point為單位處理(假如有人做的話),并不改變舊有的API的行為(isalpha之類的API可以改變,因為不影響兼容性)。跟現實世界 javascript:void(0)一樣,歷史問題總是不能完美解決的。

    最后推薦一本書,O'REILLY出版的Fonts & Encoding,前半部分關于unicode的討論可以學到不少關于unicode的知識。


    評論

    # re: [轉] 復雜的Unicode,疑惑的Python  回復  更多評論   

    2009-10-26 21:21 by 埃保常
    unicode太復雜了。以前自己以為搞懂過,后來就忘了。沒治
    主站蜘蛛池模板: 一本久久A久久免费精品不卡| 久久亚洲色WWW成人欧美| 久久久WWW成人免费精品| 亚洲第一网站男人都懂| 亚洲av无码无线在线观看| 暖暖日本免费在线视频| 亚洲av无码专区在线观看下载| 成人五级毛片免费播放| 亚洲精品无码一区二区| 国产午夜鲁丝片AV无码免费| 亚洲av永久无码精品秋霞电影秋| 国产精品国产午夜免费福利看| 亚洲高清乱码午夜电影网| 亚洲AV无码一区二区三区在线观看| 免费人成网上在线观看| 色噜噜AV亚洲色一区二区| a级黄色毛片免费播放视频| 久久亚洲免费视频| 青草草色A免费观看在线| 亚洲一区二区三区高清在线观看| 在线免费观看毛片网站| 一级女性全黄生活片免费看| 亚洲国产精品高清久久久| 99re在线视频免费观看| 亚洲中文无码亚洲人成影院| 亚洲精品无码你懂的网站| 一区二区三区无码视频免费福利| 亚洲制服丝袜第一页| 亚洲Aⅴ无码专区在线观看q| 95免费观看体验区视频| 亚洲av最新在线观看网址| 亚洲综合国产精品第一页| 51精品视频免费国产专区| 亚洲欧美一区二区三区日产| jlzzjlzz亚洲乱熟在线播放| 中文字幕免费高清视频| 人人狠狠综合久久亚洲| 老汉色老汉首页a亚洲| 亚洲AV之男人的天堂| 免费在线看黄的网站| 久久亚洲欧美国产精品|