字符串(深入Python3)

2021-02-14 Python程式設計師

Im telling you this cause youre one of my friends.
My alphabet starts where your alphabet ends!
-- Dr. Seuss, On Beyond Zebra!

在開始之前需要掌握的一些知識

你是否知道 Bougainville 人有世界上最小的字母表?他們的 Rotokas 字母表只包含了12個字母: A, E, G, I, K, O, P, R, S, T, U, 和 V。另一方面,像漢語,日語和韓語這些語言,它們則有成千上萬個字符。當然啦,英語共有26個字母 -- 如果把大寫和小寫分別計算的話,52個 -- 外加少量的標點符號,比如!@#$%&

當人們說起文本,他們通常指顯示在屏幕上的字符或者其他的記號;但是計算機不能直接處理這些字符和標記;它們只認識位(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代表 ώ。)當然,你也會想要搜索這些文檔。

現在,你就哭吧,因為以前所了解的關於字符串的知識都是錯的,根本就沒有所謂的純文本。

Unicode

Unicode入門。

Unicode編碼系統為表達任意語言的任意字符而設計。它使用4位元組的數字來表達每個字母、符號,或者表意文字(ideograph)。每個數字代表唯一的至少在某種語言中使用的符號。(並不是所有的數字都用上了,但是總數已經超過了65535,所以2個字節的數字是不夠用的。)被幾種語言共用的字符通常使用相同的數字來編碼,除非存在一個在理的語源學(etymological)理由使不這樣做。不考慮這種情況的話,每個字符對應一個數字,每個數字對應一個字符。即不存在二義性。不再需要記錄模式了。U+0041總是代表"A",即使這種語言沒有"A"這個字符。

初次面對這個創想,它看起來似乎很偉大。一種編碼方式即可解決所有問題。文檔可包含多種語言。不再需要在各種編碼方式之間進行模式轉換。但是很快,一個明顯的問題跳到我們面前。4個字節?只為了單獨一個字符 這似乎太浪費了,特別是對像英語和西語這樣的語言,他們只需要不到1個字節即可以表達所需的字符。事實上,對於以象形為基礎的語言(比如中文)這種方法也有浪費,因為這些語言的字符也從來不需要超過2個字節即可表達。

有一種Unicode編碼方式每1個字符使用4個字節。它叫做UTF-82,因為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 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)字符串。

格式化字符串

我們再來看一看humansize.py:

[download humansize.py]

SUFFIXES = {1000: ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
1024: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
"""Convert a file size to human-readable form.

Keyword arguments:
size -- file size in bytes
a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
if False, use multiples of 1000

Returns: string

"""
if size < 0:
raise ValueError("number must be non-negative")

multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in SUFFIXES[multiple]:
size /= multiple
if size < multiple:
return "{0:.1f} {1}".format(size, suffix)

raise ValueError("number too large")

"KB", "MB", "GB"&hellip; 這些是字符串。

函數的文檔字符串(docstring)也是字符串。當前的文檔字符串佔用了多行,所以它使用了相鄰的3個引號來標記字符串的起始和終止。

這3個引號代表該文檔字符串的終止。

這是另外一個字符串,作為一個可讀的提示信息傳遞給異常。

瓦哦&hellip;那是什麼?

Python 3支持把值格式化(format)成字符串。可以有非常複雜的表達式,最基本的用法是使用單個佔位符(placeholder)將一個值插入字符串。


>>> username = "mark"
>>> password = "PapayaWhip"
>>> "{0}"s password is {1}".format(username, password)
"mark"s password is PapayaWhip"

不,PapayaWhip真的不是我的密碼。

這裡包含了很多知識。首先,這裡使用了一個字符串字面值的方法調用。字符串也是對象,對象則有其方法。其次,整個表達式返回一個字符串。最後,{0}和{1}叫做替換欄位(replacement field),他們會被傳遞給format()方法的參數替換。

複合欄位名

在前一個例子中,替換欄位只是簡單的整數,這是最簡單的用法。整型替換欄位被當做傳給format()方法的參數列表的位置索引。即,{0}會被第一個參數替換(在此例中即username),{1}被第二個參數替換(password),&c。可以有跟參數一樣多的替換欄位,同時你也可以使用任意多個參數來調用format()。但是替換欄位遠比這個強大。


>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]
>>> si_suffixes
["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
>>> "1000{0[0]} = 1{0[1]}".format(si_suffixes)
"1000KB = 1MB"

不需要調用humansize模塊定義的任何函數我們就可以抓取到其所定義的數據結構:國際單位制(SI, 來自法語Système International)的後綴列表(以1000為進位)。

這一句看上去有些複雜,其實不是這樣的。{0}代表傳遞給format()方法的第一個參數,即si_suffixes。注意si_suffixes是一個列表。所以{0[0]}指代si_suffixes的第一個元素,即"KB"。同時,{0[1]}指代該列表的第二個元素,即:"MB"。大括號以外的內容 -- 包括1000,等號,還有空格等 -- 則按原樣輸出。語句最後返回字符串為"1000KB = 1MB"。

{0}會被format()的第1個參數替換,{1}則被其第2個參數替換。

這個例子說明格式說明符可以通過利用(類似)Python的語法訪問到對象的元素或屬性。這就叫做複合欄位名(compound field names)。以下複合欄位名都是有效的。

為了使你確信的確如此,下面這個樣例就組合使用了上面所有方法:


>>> import humansize
>>> import sys
>>> "1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}".format(sys)
"1MB = 1000KB"

下面是描述它如何工作的:

sys模塊保存了當前正在運行的Python實例的信息。由於已經導入了這個模塊,因此可以將其作為format()方法的參數。所以替換域{0}指代sys模塊。

sys.modules is a dictionary of all the modules that have been imported in this Python instance. The keys are the module names as strings; the values are the module objects themselves. So the replacement field {0.modules} refers to the dictionary of imported modules.sys.modules是一個保存當前Python實例中所有已經導入模塊的字典。模塊的名字作為字典的鍵;模塊自身則是鍵所對應的值。所以{0.modules}指代保存當前己被導入模塊的字典。

sys.modules["humansize"]即剛才導入的humansize模塊。所以替換域{0.modules[humansize]}指代humansize模塊。請注意以上兩句在語法上輕微的不同。在實際的Python代碼中,字典sys.modules的鍵是字符串類型的;為了引用它們,我們需要在模塊名周圍放上引號(比如 "humansize")。但是在使用替換域的時候,我們在省略了字典的鍵名周圍的引號(比如 humansize)。在此,我們引用PEP 3101:字符串格式化高級用法,解析鍵名的規則非常簡單。如果名字以數字開頭,則它被當作數字使用,其他情況則被認為是字符串。

sys.modules["humansize"].SUFFIXES是在humansize模塊的開頭定義的一個字典對象。 {0.modules[humansize].SUFFIXES}即指向該字典。

sys.modules["humansize"].SUFFIXES[1000]是一個SI(國際單位制)後綴列表:["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]。所以替換域{0.modules[humansize].SUFFIXES[1000]}指向該列表。

sys.modules["humansize"].SUFFIXES[1000][0]即SI後綴列表的第一個元素:"KB"。因此,整個替換域{0.modules[humansize].SUFFIXES[1000][0]}最後都被兩個字符KB替換。

格式說明符

但是,還有一些問題我們沒有講到!再來看一看humansize.py中那一行奇怪的代碼:

if size < multiple:
return "{0:.1f} {1}".format(size, suffix)

{1}會被傳遞給format()方法的第二個參數替換,即suffix。但是{0:.1f}是什麼意思呢?它其實包含了兩方面的內容:{0}你已經能理解,:.1f則不一定了。第二部分(包括冒號及其後邊的部分)即格式說明符(format specifier),它進一步定義了被替換的變量應該如何被格式化。

格式說明符的允許你使用各種各種實用的方法來修飾被替換的文本,就像C語言中的printf()函數一樣。我們可以添加使用零填充(zero-padding),襯距(space-padding),對齊字符串(align strings),控制10進位數輸出精度,甚至將數字轉換成16進位數輸出。

在替換域中,冒號(:)標示格式說明符的開始。.1的意思是四捨五入到保留一們小數點。f的意思是定點數(與指數標記法或者其他10進位數表示方法相對應)。因此,如果給定size為698.24,suffix為"GB",那麼格式化後的字符串將是"698.2 GB",因為698.24被四捨五入到一位小數表示,然後後綴"GB"再被追加到這個串最後。


>>> "{0:.1f} {1}".format(698.24, "GB")
"698.2 GB"

想了解格式說明符的複雜細節,請參閱Python官方文檔關于格式化規範的迷你語言

其他常用字符串方法

除了格式化,關於字符串還有許多其他實用的使用技巧。


>>> s = """Finished files are the re-
... sult of years of scientif-
... ic study combined with the
... experience of years."""
>>> s.splitlines()
["Finished files are the re-", "sult of years of scientif-", "ic study combined with the", "experience of years."]
>>> print(s.lower())
finished files are the re-sult of years of scientif-ic study combined with theexperience of years.
>>> s.lower().count("f")
6

我們可以在Python的交互式shell裡輸入多行(multiline)字符串。一旦我們以三個引號標記多行字符串的開始,按ENTER鍵,Python shell會提示你繼續這個字符串的輸入。連續輸入三個結束引號以終止該字符串的輸入,再敲ENTER鍵則會執行該條命令(在當前例子中,把這個字符串賦給變量s)。

splitlines()方法以多行字符串作為輸入,返回一個由字符串組成的列表,列表的元素即原來的單行字符串。請注意,每行行末的回車符沒有被包括進去。

lower()方法把整個字符串轉換成小寫的。(類似地,upper()方法執行大寫化轉換操作。)

count()方法對串中的指定的子串進行計數。是的,在那一句中確實出現了6個字母f。

還有一種經常會遇到的情況。比如有如下形式的鍵-值對列表 key1=value1&key2=value2,我們需要將其分離然後產生一個這樣形式的字典{key1: value1, key2: value2}。


>>> query = "user=pilgrim&database=master&password=PapayaWhip"
>>> a_list = query.split("&")
>>> a_list
["user=pilgrim", "database=master", "password=PapayaWhip"]
>>> a_list_of_lists = [v.split("=", 1) for v in a_list]
>>> a_list_of_lists
[["user", "pilgrim"], ["database", "master"], ["password", "PapayaWhip"]]
>>> a_dict = dict(a_list_of_lists)
>>> a_dict
{"password": "PapayaWhip", "user": "pilgrim", "database": "master"}

split()方法使用一個參數,即指定的分隔符,然後根據這個分隔符將串分離成一個字符串列表。此處,分隔符即字符&,它還可以是其他的內容。

現在我們有了一個字符串列表,其中的每個串由三部分組成:鍵,等號和值。我們可以使用列表解析來遍歷整個列表,然後利用第一個等號標記將每個字符串再分離成兩個子串。(理論上,值也可以包含等號標記,如果執行"key=value=foo".split("="),那麼我們會得到一個三元素列表["key", "value", "foo"]。)

最後,通過調用dict()函數Python會把那個包含列表的列表(list-of-lists)轉換成字典對象。

上一個例子跟解析URL的請求參數(query parameters)很相似,但是真實的URL解析實際上比這個複雜得多。如果需要處理URL請求參數,我們最好使用urllib.parse.parse_qs()函數,它可以處理一些不常見的邊緣情況。

字符串的分片

定義一個字符串以後,我們可以截取其中的任意部分形成新串。這種操作被稱作字符串的分片(slice)。字符串分片跟列表的分片(slicing lists)原理是一樣的,從直觀上也說得通,因為字符串本身就是一些字符序列。


>>> a_string = "My alphabet starts where your alphabet ends."
>>> a_string[3:11]
"alphabet"
>>> a_string[3:-3]
"alphabet starts where your alphabet en"
>>> a_string[0:2]
"My"
>>> a_string[:18]
"My alphabet starts"
>>> a_string[18:]
" where your alphabet ends."

我們可以通過指定兩個索引值來獲得原字符串的一個slice。該操作的返回值是一個新串,依次包含了從原串中第一個索引位置開始,直到但是不包含第二個索引位置之間的所有字符。

就像給列表做分片一樣,我們也可以使用負的索引值來分片字符串。

字符串的下標索引是從0開始的,所以a_string[0:2]會返回原字符串的前兩個元素,從a_string[0]開始,直到但不包括a_string[2]。

如果省略了第一個索引值,Python會默認它的值為0。所以a_string[:18]跟a_string[0:18]的效果是一樣的,因為從0開始是被Python默認的。

同樣地,如果第2個索引值是原字符串的長度,那麼我們也可以省略它。所以,在此處a_string[18:]跟a_string[18:44]的結果是一樣的,因為這個串的剛好有44個字符。這種規則存在某種有趣的對稱性。在這個由44個字符組成的串中,a_string[:18]會返回前18個字符,而a_string[18:]則會返回除了前18個字符以外字符串的剩餘部分。事實上a_string[:n]總是會返回串的前n個字符,而a_string[n:]則會返回其餘的部分,這與串的長度無關。

String vs. Bytes

字節即字節;字符是一種抽象。一個不可變(immutable)的Unicode編碼的字符序列叫做string。一串由0到255之間的數字組成的序列叫做bytes對象。


>>> by = b"abcde"
>>> by
b"abcde"
>>> type(by)
<class "bytes">
>>> len(by)
5
>>> by += b"ÿ"
>>> by
b"abcdeÿ"
>>> len(by)
6
>>> by[0]
97
>>> by[0] = 102
Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: "bytes" object does not support item assignment

使用byte字面值語法b""來定義bytes對象。byte字面值裡的每個字節可以是ASCII字符或者是從到ÿ編碼了的16進位數。

bytes對象的類型是bytes。

跟列表和字符串一樣,我們可以通過內置函數len()來獲得bytes對象的長度。

使用+操作符可以連接bytes對象。操作的結果是一個新的bytes對象。

連接5個字節的和1個字節的bytes對象會返回一個6位元組的bytes對象。

一如列表和字符串,可以使用下標記號來獲取bytes對象中的單個字節。對字符串做這種操作獲得的元素仍為字符串,而對bytes對象做這種操作的返回值則為整數。確切地說,是0&ndash;255之間的整數。

bytes對象是不可變的;我們不可以給單個字節賦上新值。如果需要改變某個字節,可以組合使用字符串的切片和連接操作(效果跟字符串是一樣的),或者我們也可以將bytes對象轉換為bytearray對象。


>>> by = b"abcde"
>>> barr = bytearray(by)
>>> barr
bytearray(b"abcde")
>>> len(barr)
5
>>> barr[0] = 102
>>> barr
bytearray(b"fbcde")

使用內置函數bytearray()來完成從bytes對象到可變的bytearray對象的轉換。

所有對bytes對象的操作也可以用在bytearray對象上。

有一點不同的就是,我們可以使用下標標記給bytearray對象的某個字節賦值。並且,這個值必須是0&ndash;255之間的一個整數。

我們決不應該這樣混用bytes和strings。


>>> by = b"d"
>>> s = "abcde"
>>> by + s
Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: can"t concat bytes to str
>>> s.count(by)
Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: Can"t convert "bytes" object to str implicitly
>>> s.count(by.decode("ascii"))
1

不能連接bytes對象和字符串。他們兩種不同的數據類型。

也不允許針對字符串中bytes對象的出現次數進行計數,因為串裡面根本沒有bytes。字符串是一系列的字符序列。也許你是想要先把這些字節序列通過某種編碼方式進行解碼獲得字符串,然後對該字符串進行計數?可以,但是需要顯式地指明它。Python 3不會隱含地將bytes轉換成字符串,或者進行相反的操作。

好巧啊&hellip;這一行代碼剛好給我們演示了使用特定編碼方式將bytes對象轉換成字符串後該串的出現次數。

所以,這就是字符串與字節數組之間的聯繫了:bytes對象有一個decode()方法,它使用某種字符編碼作為參數,然後依照這種編碼方式將bytes對象轉換為字符串,對應地,字符串有一個encode()方法,它也使用某種字符編碼作為參數,然後依照它將串轉換為bytes對象。在上一個例子中,解碼的過程相對直觀一些 -- 使用ASCII編碼將一個字節序列轉換為字符串。同樣的過程對其他的編碼方式依然有效 -- 傳統的(非Unicode)編碼方式也可以,只要它們能夠編碼串中的所有字符。


>>> a_string = "深入 Python"
>>> len(a_string)
9
>>> by = a_string.encode("utf-8")
>>> by
b"深入 Python"
>>> len(by)
13
>>> by = a_string.encode("gb18030")
>>> by
b"ÉîÈë Python"
>>> len(by)
11
>>> by = a_string.encode("big5")
>>> by
b"²`¤J Python"
>>> len(by)
11
>>> roundtrip = by.decode("big5")
>>> roundtrip
"深入 Python"
>>> a_string == roundtrip
True

a_string是一個字符串。它有9個字符。

by是一個bytes對象。它有13個字節。它是通過a_string使用UTF-8編碼而得到的一串字節序列。

by還是一個bytes對象。它有11個字節。它是通過a_string使用GB18030編碼而得到的一串字節序列。

此時的by仍舊是一個bytes對象,由11個字節組成。它又是一種完全不同的字節序列,我們通過對a_string使用Big5編碼得到。

roundtrip是一個字符串,共有9個字符。它是通過對by使用Big5解碼算法得到的一個字符序列。並且,從執行結果可以看出,roundtrip與a_string是完全一樣的。

補充內容: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 -*-

了解更多信息,請參閱PEP263: 指定Python源碼的編碼方式。

進一步閱讀

關於Python中的Unicode:

關於Unicode本身:

關於其他的編碼方式:

關於字符串及其格式化:

string -- 常用字符串操作

格式化字符串的語法

關于格式化規範的迷你語言

PEP3101: 字符串格式化高級應用

相關焦點

  • 十六、深入Python字符串
    「@Author :Runsen」python日常處理字符串較多,本文總結一下Python的日常使用。創建字符串name = 'Runsen'name[0]'R'name[1:3]'un'for char in name:    print(char)   Runsen
  • python之字符串詳解
    大多數人學習的第一門程式語言是C/C++,個人覺得C/C++也許是小白入門的最合適的語言,但是必須承認C/C++確實有的地方難以理解,初學者如果沒有正確理解,就可能會在使用指針等變量時候變得越來越困惑,進而減少對於編程的興趣,但是不可否認,一個程式設計師對於語言的深入理解是必備技能。
  • 10 個 Python 字符串處理技巧
    那麼可以通過這個字符串處理入門教程,來了解一下利用Python處理字符串的一些基本操作。當前,自然語言處理和文本分析是研究和應用的熱點領域。這些領域包括各種具體的技能和概念,在深入具體實踐之前需要對它們有徹底的理解,為此,必須掌握一些基本的字符串操作和處理技巧。在我看來,必須掌握兩種字符串處理技巧:首先是正則表達式,一種基於模式的文本匹配方法。
  • 深入理解Python字符串的用法
    l.split(' ',3)>>> ['Hi', 'there', ',', 'my name is     Python 貓Do you like me ?']l.split(maxsplit=3)>>> ['Hi', 'there', ',', 'my name is     Python 貓Do you like me ?']
  • python字符串格式化深入詳解(四種方法)
    前言:本文詳細整理了python字符串格式化的幾種方式。
  • Python字符串
    字符串序列用於表示和存儲文本,python中字符串是不可變對象。
  • 13-python中的字符串
    通過前兩天的文章12-python中的集合我們學習了有關集合的知識,今天我們將學習一下python中的字符串。(一)字符串的介紹    字符串,是python中的基本數據類型,是一個不可變的字符序列。    字符串的駐留機制,是僅保留一份相同且不可變字符串的一種方法。
  • 【語言學習】python——字符串
    字符串的基本操作圖源:清華大學公眾號•python中一共提供了2類4中字符串的表示方法,如果希望在字符串中包含雙引號或者單引號。那麼如果想表示雙引號的話,字符串就用單引號,反之亦然。     •字符串的序號有正向遞增序號和反向遞減序號•正向遞增從0開始;•反向遞減從-1開始;•索引:返回字符串中單個字符 <字符串>[M].
  • 秘籍:10個Python字符串處理技巧(附代碼)
    這些領域包括各種具體的技能和概念,在深入具體實踐之前需要對它們有徹底的理解,為此,必須掌握一些基本的字符串操作和處理技巧。在我看來,必須掌握兩種字符串處理技巧:首先是正則表達式,一種基於模式的文本匹配方法。關於正則表達式有許多精彩的介紹,但是喜歡通過視頻學習的朋友仍然可以從這個視頻中受益良多:https://youtu.be/Q1zLqfnEXdw?
  • python爬蟲-字符串
    python字符串 Python中的字符串可以使用單引號、雙引號和三引號(三個單引號或三個雙引號,可以換行的)括起來,使用反斜槓 \ 轉義特殊字符
  • python爬蟲 - 字符串
    python字符串Python中的字符串可以使用單引號、雙引號和三引號(三個單引號或三個雙引號,可以換行的)括起來,使用反斜槓 \ 轉義特殊字符Python3源碼文件默認以UTF-8編碼,所有字符串都是unicode字符串支持字符串拼接、截取等多種運算
  • 獨家 | 秘籍:10個Python字符串處理技巧(附代碼)
    這些領域包括各種具體的技能和概念,在深入具體實踐之前需要對它們有徹底的理解,為此,必須掌握一些基本的字符串操作和處理技巧。在我看來,必須掌握兩種字符串處理技巧:首先是正則表達式,一種基於模式的文本匹配方法。關於正則表達式有許多精彩的介紹,但是喜歡通過視頻學習的朋友仍然可以從這個視頻中受益良多:https://youtu.be/Q1zLqfnEXdw?
  • Python字符串的45個方法詳解
    python".find('o')3#索引起始位置為4 索引範圍為:ve python"I love python".find('o',4)11#索引起始位置為4,結束位置為12 索引範圍為:ve pytho"I love python".find('o',4,12)11"I love python".find('o',4,11)#不包括11位的'o',返回-1
  • Python字符串總結
    @Author :RunsenPython字符串總結什麼字符串字符串是由獨立字符組成的一個序列,通常包含在單引號(『 』),雙引號(」「)三引號(''' ''')s1itemitem2: 2nd itemReturns:similarity score between item1 and item2"""轉義字符用 \ 開頭的字符串
  • Python基礎知識點手冊——字符串方法(二)
    >ValueError Traceback (most recent call last)<ipython-input-3-4252de1acf66> in <module>----> 1 'pythonpython'.index('pt')ValueError
  • Python字符串處理的8招秘籍
    Python的字符串處理,在爬蟲的數據解析、大數據的文本清洗,以及普通文件處理等方面應用非常廣泛,而且Python對字符串的處理內置了很多高效的函數,功能非常強大、使用非常方便。今天我就把字符串處理時用到最多的方法總結分享給大家,希望大家可以輕鬆應對字符串處理。
  • Python3的字符串類型(瘋狂Python)
    「Hello,田心木瓜」是一個字符串。In [3]: ss = 'ABCDEFGHIJ' #定義一個字符串In [7]: ss[2]Out[7]: 'C'raw_input()是python 2中的,相當於python 3中的inputIn [27]: i = input()1
  • 深入 Python 解釋器源碼,我終於搞明白了字符串駐留的原理!
    在本文中,我們將深入研究 Python 的內部實現,並了解 Python 如何使用一種名為字符串駐留(String Interning)的技術,實現解釋器的高性能。 如果沒有駐留,當我們要比較兩個字符串是否相等時,它的時間複雜度將上升到 O(n),即需要檢查兩個字符串中的每個字符,才能判斷出它們是否相等。但是,如果字符串是固定的,由於相同的字符串將使用同一個對象引用,因此只需檢查指針是否相同,就足以判斷出兩個字符串是否相等,不必再逐一檢查每個字符。
  • Python 入門 – 使用字符串
    Python 3將字符串定義為「文本序列類型」。你可以使用內置的str()函數將其他類型轉換為字符串。在本文中,你將學習如何:創建字符串字符串方法字符串格式化字符串連接字符串切片讓我們從學習創建字符串的不同方法開始吧!
  • Python字符串拼接(包含字符串拼接數字)
    在 Python 中拼接(連接)字符串很簡單,可以直接將兩個字符串緊挨著寫在一起,具體格式為:strname = "str1" "