標籤:
文本,他們通常指顯示在螢幕上的字元或者其他的記號;但是電腦不能直接處理這些字元和標記;它們只認識位(bit)和位元組(byte)。實際上,從螢幕上的每一塊文本都是以某種 字元編碼(character encoding) 的方式儲存的。粗略地說就是,字元編碼提供一種映射,使螢幕上顯示的內容和記憶體、磁碟記憶體儲的內容對應起來。有許多種不同的字元編碼,有一些是為特定的語言,比如俄語、中文或者英語,設計、最佳化的,另外一些則可以用於多種語言的編碼。
在實際操作中則會比上邊描述的更複雜一些。許多字元在幾種編碼裡是共用的,但是在實際的記憶體或者磁碟上,不同的編碼方式可能會使用不同的位元組序 列來儲存他們。所以,你可以把字元編碼當做一種解碼密鑰。當有人給你一個位元組序列 -- 檔案,網頁,或者別的什麼 -- 並且告訴你它們是文本時,就需要知道他們使用了何種編碼方式,然後才能將這些位元組序列解碼成字元。如果他們給的是錯誤的密鑰或者根本沒有給你密鑰,那就得 自己來破解這段編碼,這可是一個艱難的任務。有可能你使用了錯誤的解碼方式,然後出現一些莫名其妙的結果。
你所瞭解的關於字串的知識都是錯的。
你肯定見過這樣的網頁,在撇號( " )該出現的地方被奇怪的像問號的字元替代了。這種情況通常意味著頁面的作者沒有正確的聲明其使用的編碼方式,瀏覽器只能自己來猜測,結果就是一些正確的和意料之外的字元的混合體。如果原文是英語,那隻是不方便閱讀而已;在其他的語言環境下,結果可能是完全不可讀的。
現有的字元編碼各類給世界上每種主要的語言都提供了編碼方案。由於每種語言的各不相同,而且在以前記憶體和硬碟都很昂貴,所以每種字元編碼都為特定的語言做 了最佳化。上邊這句話的意思是,每種編碼都使用數字(0–255)來代表這種語言的字元。比如,你也許熟悉 ASCII 編碼,它將英語中的字元都當做從0–127的數字來儲存。(65表示大寫的A,97表示小寫a, & c。)英語的字母表很簡單,所以它能用不到128個數字表達出來。如果你懂得2進位計數的話,它只使用了一個位元組內的7位。
西歐的一些語言,比如法語,西班牙語和德語等,比英語有更多的字母。或者,更準確的說,這些語言含有與變音符號(diacritical marks)組合起來的字母,像西班牙語裡的 ñ 。這些語言最常用的編碼方式是CP-1252,又叫做windows-1252,因為它在微軟的視窗作業系統上被廣泛使用。CP-1252和 ASCII 在0–127這個範圍內的字元是一樣的,但是CP-1252為 ñ (n-with-a-tilde-over-it, 241), Ü (u-with-two-dots-over-it, 252)這類字元而擴充到了128–255這個範圍。然而,它仍然是一種單位元組的編碼方式;可能的最大數字為255,這仍然可以用一個位元組來表示。
然而,像中文,日語和韓語等語言,他們的字元如此之多而不得不需要多位元組編碼的字元集。即,使用兩個位元組的數字 (0–255)代表每個字元。但是就跟不同的單位元組編碼方式一樣,多位元組編碼方式之間也有同樣的問題,即他們使用的數字是相同的,但 是表達的內容卻不同。相對於單位元組編碼方式它們只是使用的數字範圍更廣一些,因為有更多的字元需要表示。
在沒有網路的時代,文本由自己輸入,偶爾才會列印出來,大多數情況下使用以上的編碼方案是可行的。那時沒有太多的純文字。原始碼使用 ASCII 編碼,其他人也都使用文書處理器,這些文書處理器定義了他們自己的格式(非文本的),這些格式會連同字元編碼資訊和風格樣式一起記錄其中, & c。人們使用與原作者相同的文書處理軟體讀取這些文檔,所以或多或少地能夠使用。
現在,我們考慮一下像email和web這樣的全球網路的出現。大量的“純文字”檔案在全球範圍內流轉,它們在一台電腦上被撰寫出來,通過第二 台電腦進行傳輸,最後在另外一台電腦上顯示。電腦只能識別數字,但是這些數字可能表達的是其他的東西。Oh no! 怎麼辦呢。。好吧,那麼系統必須被設計成在每一段“純文字”上都搭載編碼資訊。記住,編碼方式是將電腦可讀的數字映射成人類可讀的字元的解碼密鑰。失去 解碼密鑰則意味著混亂不清的,莫名其妙的資訊,或者更糟。
現在我們考慮嘗試把多段文本儲存在同一個地方,比如放置所有收到郵件的資料庫。這仍然需要對每段文本儲存其相關的字元編碼資訊,只有這樣才能正 確地顯示它們。這很困難嗎?試試搜尋你的email資料庫,這意味著需要在運行時進行編碼之間的轉換。很有趣是吧…
現在我們來分析另外一種可能性,即多語言文檔,同一篇文檔裡來自幾種不同語言的字元混在一起。(提示:處理這樣文檔的程式通常使用轉義符在不同的模式 (modes)之間切換。噗!現在是俄語 koi8-r 模式,所以241代表 Я;噗噗!現在到了Mac Greek模式,所以241代表 ?。)當然,你也會想要搜尋 這些 文檔。根本就沒有所謂的純文字。
--------------------------------------分割線 --------------------------------------
CentOS上源碼安裝Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm
《Python核心編程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm
《Python開發技術詳解》.( 周偉,宗傑).[高清PDF掃描版+隨書視頻+代碼] http://www.linuxidc.com/Linux/2013-11/92693.htm
Python指令碼擷取Linux系統資訊 http://www.linuxidc.com/Linux/2013-08/88531.htm
在Ubuntu下用Python搭建案頭演算法交易研究環境 http://www.linuxidc.com/Linux/2013-11/92534.htm
Python 語言的發展簡史 http://www.linuxidc.com/Linux/2014-09/107206.htm
Unicode
Unicode編碼系統為表達 任意 語言的 任意 字元而設計。它使用4位元組的數字來表達每個字母、符號,或者表意文字(ideograph)。每個數字代表唯一的至少在某種語言中使用的符號。(並不是所 有的數字都用上了,但是總數已經超過了65535,所以2個位元組的數字是不夠用的。)被幾種語言共用的字元通常使用相同的數字來編碼,除非存在一個在理的 語源學(etymological)理由使不這樣做。不考慮這種情況的話,每個字元對應一個數字,每個數字對應一個字元。即不存在二義性。不再需要記錄模 式了。 U+0041 總是代表 "A" ,即使這種語言沒有 "A" 這個字元。
初次面對這個創想,它看起來似乎很偉大。一種編碼方式即可解決所有問題。文檔可包含多種語言。不再需要在各種編碼方式之間進行模式轉換。但是很 快,一個明顯的問題跳到我們面前。4個位元組?只為了單獨一個字元 這似乎太浪費了,特別是對像英語和西語這樣的語言,他們只需要不到1個位元組即可以表達所需的字元。事實上,對於以象形為基礎的語言(比如中文)這種方法也 有浪費,因為這些語言的字元也從來不需要超過2個位元組即可表達。
有一種Unicode編碼方式每1個字元使用4個位元組。它叫做 UTF-32 ,因為32位 = 4位元組。UTF-32是一種直觀的編碼方式;它收錄每一個Unicode字元(4位元組數字)然後就以那個數字代表該字元。這種方法有其優點,最重要的一點就是可以在常數時間內定位字串裡的第 N 個字元,因為第 N 個字元從第 4×Nth 個位元組開始。另外,它也有其缺點,最明顯的就是它使用4個詭異的位元組來儲存每個詭異的字元…
儘管有Unicode字元非常多,但是實際上大多數人不會用到超過前65535個以外的字元。因此,就有了另外一種Unicode編碼方式,叫做 UTF-16 (因為16位 = 2位元組)。UTF-16將0–65535範圍內的字元編碼成2個位元組,如果真的需要表達那些很少使用的星芒層(astral plane)內超過這65535範圍的Unicode字元,則需要使用一些詭異的技巧來實現。UTF-16編碼最明顯的優點是它在空間效率上比UTF- 32高兩倍,因為每個字元只需要2個位元組來儲存(除去65535範圍以外的),而不是UTF-32中的4個位元組。並且,如果我們假設某個字串不包含任何 星芒層中的字元,那麼我們依然可以在常數時間內找到其中的第 N 個字元,直到它不成立為止這總是一個不錯的推斷…
但是對於UTF-32和UTF-16編碼方式還有一些其他不明顯的缺點。不同的電腦系統會以不同的順序儲存位元組。這意味著字元 U+4E2D 在UTF-16編碼方式下可能被儲存為 4E 2D 或者 2D 4E ,這取決於該系統使用的是大尾端(big-endian)還是小尾端(little-endian)。(對於UTF-32編碼方式,則有更多種可能的位元組 排列。)只要文檔沒有離開你的電腦,它還是安全的 -- 同一台電腦上的不同程式使用相同的位元組順序(byte order)。但是當我們需要在系統之間傳輸這個文檔的時候,也許在全球資訊網中,我們就需要一種方法來指示當前我們的位元組是怎樣儲存的。不然的話,接收文檔 的電腦就無法知道這兩個位元組 4E 2D 表達的到底是 U+4E2D 還是 U+2D4E 。
為瞭解決 這個 問題,多位元組的Unicode編???方式定義了一個位元組順序標記(Byte Order Mark),它是一個特殊的非列印字元,你可以把它包含在文檔的開頭來指示你所使用的位元組順序。對於UTF-16,位元組順序標記是 U+FEFF 。如果收到一個以位元組 FF FE 開頭的UTF-16編碼的文檔,你就能確定它的位元組順序是單向的(one way)的了;如果它以 FE FF 開頭,則可以確定位元組順序反向了。
不過,UTF-16還不夠完美,特別是要處理許多 ASCII 字元時。如果仔細想想的話,甚至一個中文網頁也會包含許多的 ASCII 字元 -- 所有包圍在可列印中文字元周圍的元素(element)和屬性(attribute)。能夠在常數時間內找到第 Nth 個字元當然非常好,但是依然存在著糾纏不休的星芒層字元的問題,這意味著你不能 保證 每個字元都是2個位元組長,所以,除非你維護著另外一個索引,不然就不能 真正意義上的 在常數時間內定位第 N 個字元。另外,朋友,世界上肯定還存在很多的 ASCII 文本…
另外一些人琢磨著這些問題,他們找到了一種解決方案:
UTF-8
UTF-8是一種為Unicode設計的 變長(variable-length) 編碼系統。即,不同的字元可使用不同數量的位元組編碼。對於ASCII 字元(A-Z, & c.) UTF-8 僅使用1個位元組來編碼。事實上, UTF-8 中前128個字元(0–127)使用的是跟ASCII 一樣的編碼方式。像ñ和ö這樣的擴充拉丁字元(Extended Latin)則使用2個位元組來編碼。(這裡的位元組並不是像UTF-16中那樣簡單的Unicode編碼點(unicode code point);它使用了一些位變換(bit-twiddling)。)中文字元比如則佔用了3個位元組。很少使用的星芒層字元則佔用4個位元組。
缺點:因為每個字元使用不同數量的位元組編碼,所以尋找串中第 N 個字元是一個O(N)複雜度的操作 -- 即,串越長,則需要更多的時間來定位特定的字元。同時,還需要位變換來把字元編碼成位元組,把位元組解碼成字元。
優點:在處理經常會用到的 ASCII 字元方面非常有效。在處理擴充的拉丁字元集方面也不比UTF-16差。對於中文字元來說,比UTF-32要好。同時,(在這一條上你得相信我,因為我不打算給你展示它的數學原理。)由位操作的天性使然,使用UTF-8不再存在位元組順序的問題了。一份以 UTF-8 編碼的文檔在不同的電腦之間是一樣的位元流。
Python源碼的編碼方式
Python 3會假定我們的源碼 -- 即 .py 檔案 -- 使用的是 UTF-8 編碼方式。
Python 2裡, .py 檔案 預設 的編碼方式為 ASCII 。Python 3的源碼的預設編碼方式為 UTF-8
如果想使用一種不同的編碼方式來儲存Python代碼,我們可以在每個檔案的第一行放置編碼聲明(encoding declaration)。以下聲明定義 .py 檔案使用windows-1252編碼方式:
# -*- coding: windows-1252 -*-
從技術上說,字元編碼的重載聲明也可以放在第二行,如果第一行被類 UNIX 系統中的hash-bang命令佔用了。
#!/usr/bin/python3# -*- coding: windows-1252 -*-
瞭解更多資訊,請參閱 PEP 263: 指定Python源碼的編碼方式。
在Python 3,所有的字串都是使用Unicode編碼的字元序列。不再存在以UTF-8或者CP-1252編碼的情況。也就是說,這個字串是以 UTF-8 編碼的嗎?不再是一個有效問題。 UTF-8 是一種將字元編碼成位元組序列的方式。如果需要將字串轉換成特定編碼的位元組序列,Python 3可以為你做到。如果需要將一個位元組序列轉換成字串,Python 3也能為你做到。位元組即位元組,並非字元。字元在電腦內只是一種抽象。字串則是一種抽象的序列。
>>> s = "深入 Python">>> len(s)9>>> s[0]"深">>> s + " 3""深入 Python 3"
為了建立一個字串,將其用引號包圍。Python字串可以通過單引號( " )或者雙引號( " )來定義。
內建函數 len() 可返回字串的長度, 即 字元的個數。這與獲得列表,元組,集合或者字典的長度的函數是同一個。Python中,字串可以想像成由字元組成的元組。
Just like getting individual items out of a list, you can get individual characters out of a string using index notation.與取得列表中的元素一樣,也可以通過下標記號取得字串中的某個字元。
類似列表,可以使用 + 操作符來 串連(concatenate) 字串。
Python字串編碼