Python | 深入浅出字符串
本文授權(quán)轉(zhuǎn)自極客時間專欄:(Python核心技術(shù)與實戰(zhàn))
(2 天倒計時!掃碼購買加微信返現(xiàn) 18 元)
Python的程序中充滿了字符串(string),在平常閱讀代碼時也屢見不鮮。字符串同樣是Python中很常見的一種數(shù)據(jù)類型,比如日志的打印、程序中函數(shù)的注釋、數(shù)據(jù)庫的訪問、變量的基本操作等等,都用到了字符串。
當(dāng)然,我相信你本身對字符串已經(jīng)有所了解。今天這節(jié)課,我主要帶你回顧一下字符串的常用操作,并對其中的一些小tricks詳細(xì)地加以解釋。
字符串基礎(chǔ)
什么是字符串呢?字符串是由獨立字符組成的一個序列,通常包含在單引號('')雙引號("")或者三引號之中(''' '''或""" """,兩者一樣),比如下面幾種寫法。
city?=?'beijing'
text?=?"welcome?to?jike?shijian"
這里定義了name、city和text三個變量,都是字符串類型。我們知道,Python中單引號、雙引號和三引號的字符串是一模一樣的,沒有區(qū)別,比如下面這個例子中的s1、s2、s3完全一樣。
s2?=?"hello"
s3?=?"""hello"""
s1?==?s2?==?s3
True
Python同時支持這三種表達(dá)方式,很重要的一個原因就是,這樣方便你在字符串中,內(nèi)嵌帶引號的字符串。比如:
Python的三引號字符串,則主要應(yīng)用于多行字符串的情境,比如函數(shù)的注釋等等。
????"""
????Calculate?similarity?between?two?items
????Args:
????????item1:?1st?item
????????item2:?2nd?item
????Returns:
??????similarity?score?between?item1?and?item2
????"""
同時,Python也支持轉(zhuǎn)義字符。所謂的轉(zhuǎn)義字符,就是用反斜杠開頭的字符串,來表示一些特定意義的字符。我把常見的的轉(zhuǎn)義字符,總結(jié)成了下面這張表格。
? ? ?
為了方便你理解,我舉一個例子來說明。
print(s)
a
b????c
這段代碼中的'\n',表示一個字符——換行符;'\t'也表示一個字符——橫向制表符。所以,最后打印出來的輸出,就是字符a,換行,字符b,然后制表符,最后打印字符c。不過要注意,雖然最后打印的輸出橫跨了兩行,但是整個字符串s仍然只有5個元素。
5
在轉(zhuǎn)義字符的應(yīng)用中,最常見的就是換行符'\n'的使用。比如文件讀取,如果我們一行行地讀取,那么每一行字符串的末尾,都會包含換行符'\n'。而最后做數(shù)據(jù)處理時,我們往往會丟掉每一行的換行符。
字符串的常用操作
講完了字符串的基本原理,下面我們一起來看看字符串的常用操作。你可以把字符串想象成一個由單個字符組成的數(shù)組,所以,Python的字符串同樣支持索引,切片和遍歷等等操作。
name[0]
'j'
name[1:3]
'as'
和其他數(shù)據(jù)結(jié)構(gòu),如列表、元組一樣,字符串的索引同樣從0開始,index=0表示第一個元素(字符),[index:index+2]則表示第index個元素到index+1個元素組成的子字符串。
遍歷字符串同樣很簡單,相當(dāng)于遍歷字符串中的每個字符。
????print(char)???
j
a
s
o
n
特別要注意,Python的字符串是不可變的(immutable)。因此,用下面的操作,來改變一個字符串內(nèi)部的字符是錯誤的,不允許的。
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
TypeError:?'str'?object?does?not?support?item?assignment
Python中字符串的改變,通常只能通過創(chuàng)建新的字符串來完成。比如上述例子中,想把'hello'的第一個字符'h',改為大寫的'H',我們可以采用下面的做法:
s?=?s.replace('h',?'H')
第一種方法,是直接用大寫的'H',通過加號'+'操作符,與原字符串切片操作的子字符串拼接而成新的字符串。
第二種方法,是直接掃描原字符串,把小寫的'h'替換成大寫的'H',得到新的字符串。
你可能了解到,在其他語言中,如Java,有可變的字符串類型,比如StringBuilder,每次添加、改變或刪除字符(串),無需創(chuàng)建新的字符串,時間復(fù)雜度僅為O(1)。這樣就大大提高了程序的運行效率。
但可惜的是,Python中并沒有相關(guān)的數(shù)據(jù)類型,我們還是得老老實實創(chuàng)建新的字符串。因此,每次想要改變字符串,往往需要O(n)的時間復(fù)雜度,其中,n為新字符串的長度。
你可能注意到了,上述例子的說明中,我用的是“往往”、“通常”這樣的字眼,并沒有說“一定”。這是為什么呢?顯然,隨著版本的更新,Python也越來越聰明,性能優(yōu)化得越來越好了。
這里,我著重講解一下,使用加法操作符'+='的字符串拼接方法。因為它是一個例外,打破了字符串不可變的特性。
操作方法如下所示:
我們來看下面這個例子:
for?n?in?range(0,?100000):
????s?+=?str(n)
你覺得這個例子的時間復(fù)雜度是多少呢?
每次循環(huán),似乎都得創(chuàng)建一個新的字符串;而每次創(chuàng)建一個新的字符串,都需要O(n)的時間復(fù)雜度。因此,總的時間復(fù)雜度就為O(1) + O(2) + ... + O(n) = O(n^2)。這樣到底對不對呢?
乍一看,這樣分析確實很有道理,但是必須說明,這個結(jié)論只適用于老版本的Python了。自從Python2.5開始,每次處理字符串的拼接操作時(str1 += str2),Python首先會檢測str1還有沒有其他的引用。如果沒有的話,就會嘗試原地擴(kuò)充字符串buffer的大小,而不是重新分配一塊內(nèi)存來創(chuàng)建新的字符串并拷貝。這樣的話,上述例子中的時間復(fù)雜度就僅為O(n)了。
因此,以后你在寫程序遇到字符串拼接時,如果使用'+='更方便,就放心地去用吧,不用過分擔(dān)心效率問題了。
另外,對于字符串拼接問題,除了使用加法操作符,我們還可以使用字符串內(nèi)置的join函數(shù)。string.join(iterable),表示把每個元素都按照指定的格式連接起來。
????l.append(str(n))
l?=?'?'.join(l)?
由于列表的append操作是O(1)復(fù)雜度,字符串同理。因此,這個含有for循環(huán)例子的時間復(fù)雜度為n*O(1)=O(n)。
接下來,我們看一下字符串的分割函數(shù)split()。string.split(separator),表示把字符串按照separator分割成子字符串,并返回一個分割后子字符串組合的列表。它常常應(yīng)用于對數(shù)據(jù)的解析處理,比如我們讀取了某個文件的路徑,想要調(diào)用數(shù)據(jù)庫的API,去讀取對應(yīng)的數(shù)據(jù),我們通常會寫成下面這樣:
????"""
????given?namespace?and?table,?query?database?to?get?corresponding
????data?????????
????"""
path?=?'hive://ads/training_table'
namespace?=?path.split('//')[1].split('/')[0]?#?返回'ads'
table?=?path.split('//')[1].split('/')[1]?#?返回?'training_table'
data?=?query_data(namespace,?table)?
此外,常見的函數(shù)還有:
string.strip(str),表示去掉首尾的str字符串;
string.lstrip(str),表示只去掉開頭的str字符串;
string.rstrip(str),表示只去掉尾部的str字符串。
這些在數(shù)據(jù)的解析處理中同樣很常見。比如很多時候,從文件讀進(jìn)來的字符串中,開頭和結(jié)尾都含有空字符,我們需要去掉它們,就可以用strip()函數(shù):
s.strip()
'my?name?is?jason'
當(dāng)然,Python中字符串還有很多常用操作,比如,string.find(sub, start, end),表示從start到end查找字符串中子字符串sub的位置等等。這里,我只強(qiáng)調(diào)了最常用并且容易出錯的幾個函數(shù),其他內(nèi)容你可以自行查找相應(yīng)的文檔、范例加以了解,我就不一一贅述了。
字符串的格式化
最后,我們一起來看看字符串的格式化。什么是字符串的格式化呢?
通常,我們使用一個字符串作為模板,模板中會有格式符。這些格式符為后續(xù)真實值預(yù)留位置,以呈現(xiàn)出真實值應(yīng)該呈現(xiàn)的格式。字符串的格式化,通常會用在程序的輸出、logging等場景。
舉一個常見的例子。比如我們有一個任務(wù),給定一個用戶的userid,要去數(shù)據(jù)庫中查詢該用戶的一些信息,并返回。而如果數(shù)據(jù)庫中沒有此人的信息,我們通常會記錄下來,這樣有利于往后的日志分析,或者是線上bug的調(diào)試等等。
我們通常會用下面的方法來表示:
其中的string.format(),就是所謂的格式化函數(shù);而大括號{}就是所謂的格式符,用來為后面的真實值——變量name預(yù)留位置。如果id = '123'、name='jason',那么輸出便是:
這樣看來,是不是非常簡單呢?
不過要注意,string.format()是最新的字符串格式函數(shù)與規(guī)范。自然,我們還有其他的表示方法,比如在Python之前版本中,字符串格式化通常用%來表示,那么上述的例子,就可以寫成下面這樣:
其中%s表示字符串型,%d表示整型等等,這些屬于常識,你應(yīng)該都了解。
當(dāng)然,現(xiàn)在你寫程序時,我還是推薦使用format函數(shù),畢竟這是最新規(guī)范,也是官方文檔推薦的規(guī)范。
也許有人會問,為什么非要使用格式化函數(shù),上述例子用字符串的拼接不也能完成嗎?沒錯,在很多情況下,字符串拼接確實能滿足格式化函數(shù)的需求。但是使用格式化函數(shù),更加清晰、易讀,并且更加規(guī)范,不易出錯。
總結(jié)
這節(jié)課,我們主要學(xué)習(xí)了Python字符串的一些基本知識和常用操作,并且結(jié)合具體的例子與場景加以說明,特別需要注意下面幾點。
Python中字符串使用單引號、雙引號或三引號表示,三者意義相同,并沒有什么區(qū)別。其中,三引號的字符串通常用在多行字符串的場景。
Python中字符串是不可變的(前面所講的新版本Python中拼接操作'+='是個例外)。因此,隨意改變字符串中字符的值,是不被允許的。
Python新版本(2.5+)中,字符串的拼接變得比以前高效了許多,你可以放心使用。
Python中字符串的格式化(string.format)常常用在輸出、日志的記錄等場景。
思考題
最后,給你留一道思考題。在新版本的Python(2.5+)中,下面的兩個字符串拼接操作,你覺得哪個更優(yōu)呢?歡迎留言和我分享你的觀點,也歡迎你把這篇文章分享給你的同事、朋友。
for?n?in?range(0,?100000):
????s?+=?str(n)
l?=?[]
for?n?in?range(0,?100000):
????l.append(str(n))
s?=?'?'.join(l)
(2 天倒計時!掃碼購買加微信返現(xiàn) 18 元)
總結(jié)
以上是生活随笔為你收集整理的Python | 深入浅出字符串的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么我不建议你买保险?
- 下一篇: 数据结构与算法之递归系列