你真的知道Python的字串是什麼嗎?

Python貓發表於2018-11-19

在《詳解Python拼接字串的七種方式》這篇文章裡,我提到過,字串是程式設計師離不開的事情。後來,我看到了一個英文版本的說法:

There are few guarantees in life: death, taxes, and programmers needing to deal with strings.

它竟然把程式設計師處理字串跟死亡大事並列了,可見這是多麼命中註定……

回頭看其它文章,我發現這種說法得到了佐證,因為我在無意中已零零碎碎地提及了字串的很多方面,例如:字串讀寫檔案、字串列印、字串不可變性、字串Intern機制、字串拼接、是否會取消字串,等等。而這些,還只能算字串面目的冰山一角。

既然如此,那乾脆再單獨寫寫Python的字串吧。這篇內容可能會很基(li)礎(lun),並不是什麼“騷操作”或“冷知識”,權當是一份溫故而求知新的筆記。

1 Python字串是什麼?

根據維基百科定義:字串是由零個或多個字元組成的有限序列。而在Python 3中,它有著更明確的意思:字串是由Unicode碼點組成的不可變序列(Strings are immutable sequences of Unicode code points.)

字串是一種序列,這意味著它具備序列型別都支援的操作:

# 以下的s、t皆表示序列,x表示元素
x in s  # 若s包含x,返回True,否則返回False
x not in s  # 若s包含x,返回False,否則返回True
s + t  # 連線兩個序列
s * n  # s複製n次
s[i]   # s的索引第i項
s[i:j] # s切片從第i項到第j-1項
s[i:j:k]  #  s切片從第i項到第j-1項,間隔為k
len(s)  # s的長度
min(s)  # s的最小元素
max(s)  # s的最大元素
s.index(x) # x的索引位置
s.count(x)  # s中出現x的總次數

字串序列還具備一些特有的操作,限於篇幅,按下不表。預告一下,下一篇《你真的知道Python的字串怎麼用嗎? 》將會展開介紹,敬請期待……

字串序列是一種不可變序列,這意味著它不能像可變序列一樣,進行就地修改。例如,在字串“Python”的基礎上拼接“Cat”,得到字串“PythonCat”,新的字串是一個獨立的存在,它與基礎字串“Python”並沒有關聯關係。

basename = "Python"
myname = basename + "Cat"
id(basename) == id(myname) >>> False

# 作為對比,列表能就地修改
baselist = ["Python"]
baselist.append("Cat")
print(baselist) >>> ['Python', 'Cat']

字串這種序列與其它序列(如列表、元組)的不同之處在於,它的“元素”限定了只能是Unicode碼點。Unicode碼點是什麼呢?簡單理解,就是用Unicode編碼的字元。那字元是什麼呢?字元是人類書寫系統的各類符號,例如阿拉伯數字、拉丁字母、中文、日文、藏文、標點符號、控制符號(換行符、製表符等)、其它特殊符號(@#¥%$*等等)。那Unicode編碼又是什麼呢?Unicode別名是萬國碼、國際碼,它是一種適用性最廣的、將書寫字元編碼為計算機數字的標準。

總所周知,在最底層的計算機硬體世界裡,只有0和1。那麼,怎麼用這個二進位制數字,來表示人類的文化性的字元呢?這些字元數量龐大,而且還在日益增長與變化,什麼樣的編碼方案才是最靠譜的呢?

歷史上,人類創造了多種多樣的字元編碼標準,例如ASCII(1963年)編碼,以西歐語言的字元為主,它的缺點是隻能編碼128個字元;例如GB2312(1981年),這是中國推出的編碼標準,在相容ASCII標準的基礎上,還加入了對日文、俄文等字元的編碼,但缺點仍是編碼範圍有限,無法表示古漢語、繁體字及更多書寫系統的字元。

Unicode編碼標準於1991年推出,至今迭代到了第11版,已經能夠編碼146個書寫系統的130000個字元,可謂是無所不包,真不愧是“國際碼”。Unicode編碼其實是一個二進位制字符集,它建立了從書寫字元對映成唯一的數字字元的關係,但是,由於各系統平臺對字元的理解差異,以及出於節省空間的考慮,Unicode編碼還需要再做一次轉換,轉換後的新的二進位制數字才能作為實際儲存及網路傳輸時的編碼

這種轉換方式被稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF),它又細分為UTF-8、UTF-16、UTF-32等等方式。我們最常用的是UTF-8。為什麼UTF-8最常用呢?因為它是可變長度的編碼方案,針對不同的字元使用不同的位元組數來編碼,例如編碼英文字母時,只需要一個位元組(8個位元),而編碼較複雜的漢字時,就會用到三個位元組(24個位元)。

你真的知道Python的字串是什麼嗎?

二進位制的編碼串可以說是給機器閱讀的,為了方便,我們通常會將其轉化為十六進位制,例如“中”字的Unicode編碼可以表示成0x4e2d ,其UTF-8編碼可以表示為0xe4b8ad,'0x'用於開頭表示十六進位制,這樣就簡潔多了。不過,UTF-8編碼的結果會被表示成以位元組為單位的形式,例如“中”字用UTF-8編碼後的位元組形式是\xe4\xb8\xad

Python中為了區分Unicode編碼與位元組碼,分別在開頭加“u”和“b”以示區分。在Python 3中,因為Unicode成了預設編碼格式,所以“u”被省略掉了。

# 字元轉Unicode編碼
# Python3中,開頭的u被省略,b不可省略
hex(ord('中')) >>> '0x4e2d'
hex(ord('A'))  >>> '0x41'

# 字元轉UTF-8編碼(encode)
'中'.encode('utf-8') >>> b'\xe4\xb8\xad'
'A'.encode('utf-8')  >>> b'A'

# Unicode編碼還原成字元
chr(0x4e2d) >>> '中'
chr(0x41) >>> 'A'

# UTF-8編碼還原成字元(decode)
b'\xe4\xb8\xad'.decode('utf-8') >>> '中'
b'A'.decode('utf-8') >>> 'A'

總結一下,Python 3 中的字串是由Unicode碼點組成的不可變序列,也即是,由採用Unicode標準編碼的字元組成的不可變序列。Unicode編碼將書寫系統的字元對映成了計算機二進位制數字,為了方便,通常顯示為十六進位制;在運算記憶體中,字元以Unicode編碼呈現,當寫入磁碟或用於網路傳輸時,一般採用UTF-8方式編碼。

在Python 2中,因為歷史包袱,即Python先於Unicode編碼而誕生,所以其編碼問題是個大難題。幸好拋棄Python 2已成大勢所趨,所以我就不再對此做介紹或比對了。

2 Python字串 VS Java字串

雖然不提縱向版本間的差異,但是,我想將Python字串與其它程式語言做一個橫向對比。我覺得這會是挺好玩的事。透過跨語言的比較,也許我們能加深對一個事物(字串)的理解,還可能受到啟發,得到對“程式語言”及“程式設計哲學”的領悟。

由於本人才疏學淺,本文就只對兩點皮毛特性作說明,歡迎讀者斧正和補充。

(1)字串的定義方式

Python的字串是內建型別,所以使用起來很方便,有如下三種定義方式:

str_0 = '''Python字串可以寫在用三引號對內,表示多行字串。
還可以寫在單引號對內,
當然還可以寫在雙引號對內。
'''

str_1 = 'Python貓是一隻貓'
str_2 = "Python貓是一個微信公眾號"

Java的字串不是內建型別,它屬於物件,需要透過String類來建立。不過,正因為字串太常用,所以Java特意預定義了一個字串類String,使得程式設計師也可以像這樣來定義:String name = "Python貓"; ,而不必這樣寫:String name = new String("Python貓");

Java的字串只能寫在雙引號內,不具備Python中單雙引號混用的靈活。至於三引號的多行字串表示法,Java程式設計師表示羨慕得要死,那種痛苦,受過折磨的人最懂。寫出來讓Python程式設計師開心一下:

String s = "Java 的多行字串很麻煩,\n"
         + "既要使用換行符,\n"
         + "還需要使用加號拼接";

為什麼Java不支援多行字串、什麼時候支援多行字串?此類問題在Python程式設計師眼裡,可能很費解,但它絕對能排進“Java程式設計師最希望能實現的特性”的前列。好不容易,官方有計劃在Java 11 實現,但今年9月釋出的Java 11 仍是沒有,現在改計劃到Java 12 了。

(2)單個字元與字元序列

Java中其實也有單引號的使用,用在char型別上,例如char c = 'A'; 。char是一種內建型別,表示單個用Unicode編碼的字元。Python中沒有char型別,字串型別通吃一切。

前面說到,Python的字串是一種字元序列,而Java的字串並不是一種序列,要表示相近的概念的話,就得用到字元陣列 或者 字串陣列 ,例如:

char[] a = { 'a', 'b', 'c'};  
String[] str = new String[]{"1","2","3"}; 

字元陣列和字串陣列是一種序列,但並不是字串,它們之間如果要相互轉換,還是挺麻煩的。另外,說是序列,但Java的序列操作絕對無法跟Python相比,別的不說,就上面提及的幾個基礎操作,試問Java能否實現、實現起來要花費多大力氣?

最後來個Ending,關於“Python字串到底是什麼”就說到這啦,希望對你有所幫助。下次,我再跟大家說說“Python字串到底怎麼用”,敬請期待。

相關文章