Python 的程序中充滿了字符串(string)愕够,在平常閱讀代碼時(shí)也屢見(jiàn)不鮮过咬。字符串同樣是 Python 中很常見(jiàn)的一種數(shù)據(jù)類(lèi)型浦箱,比如日志的打印吸耿、程序中函數(shù)的注釋、數(shù)據(jù)庫(kù)的訪問(wèn)酷窥、變量的基本操作等等咽安,都用到了字符串。當(dāng)然蓬推,我相信你本身對(duì)字符串已經(jīng)有所了解妆棒。這節(jié),主要回顧一下字符串的常用操作沸伏,并對(duì)其中的一些小 tricks 詳細(xì)地加以解釋糕珊。
字符串基礎(chǔ)
什么是字符串呢?字符串是由獨(dú)立字符組成的一個(gè)序列毅糟,通常包含在單引號(hào)('')雙引號(hào)("")或者三引號(hào)之中(''' '''或""" """红选,兩者一樣),比如下面幾種寫(xiě)法姆另。
name = 'jason'
city = 'beijing'
text = "welcome to jike shijian"
這里定義了 name喇肋、city 和 text 三個(gè)變量坟乾,都是字符串類(lèi)型。我們知道蝶防,Python 中單引號(hào)甚侣、雙引號(hào)和三引號(hào)的字符串是一模一樣的,沒(méi)有區(qū)別间学,比如下面這個(gè)例子中的 s1殷费、s2、s3 完全一樣菱鸥。
s1 = 'hello'
s2 = "hello"
s3 = """hello"""
s1 == s2 == s3
True
Python 同時(shí)支持這三種表達(dá)方式宗兼,很重要的一個(gè)原因就是躏鱼,這樣方便你在字符串中氮采,內(nèi)嵌帶引號(hào)的字符串。比如:
"I'm a student"
Python 的三引號(hào)字符串染苛,則主要應(yīng)用于多行字符串的情境鹊漠,比如函數(shù)的注釋等等。
def calculate_similarity(item1, item2):
"""
Calculate similarity between two items
Args:
item1: 1st item
item2: 2nd item
Returns:
similarity score between item1 and item2
"""
同時(shí)茶行,Python 也支持轉(zhuǎn)義字符躯概。所謂的轉(zhuǎn)義字符,就是用反斜杠開(kāi)頭的字符串畔师,來(lái)表示一些特定意義的字符娶靡。我把常見(jiàn)的的轉(zhuǎn)義字符,總結(jié)成了下面這張表格看锉。
為了方便你理解姿锭,我舉一個(gè)例子來(lái)說(shuō)明。
s = 'a\nb\tc'
print(s)
a
b c
這段代碼中的'\n'伯铣,表示一個(gè)字符——換行符呻此;'\t'也表示一個(gè)字符——橫向制表符。所以腔寡,最后打印出來(lái)的輸出焚鲜,就是字符 a,換行放前,字符 b忿磅,然后制表符,最后打印字符 c凭语。不過(guò)要注意葱她,雖然最后打印的輸出橫跨了兩行,但是整個(gè)字符串 s 仍然只有 5 個(gè)元素叽粹。
len(s)
5
在轉(zhuǎn)義字符的應(yīng)用中却舀,最常見(jiàn)的就是換行符'\n'的使用。比如文件讀取锤灿,如果我們一行行地讀取挽拔,那么每一行字符串的末尾,都會(huì)包含換行符'\n'但校。而最后做數(shù)據(jù)處理時(shí)螃诅,我們往往會(huì)丟掉每一行的換行符。
字符串的常用操作
講完了字符串的基本原理状囱,下面我們一起來(lái)看看字符串的常用操作术裸。你可以把字符串想象成一個(gè)由單個(gè)字符組成的數(shù)組,所以亭枷,Python 的字符串同樣支持索引袭艺,切片和遍歷等等操作。
name = 'jason'
name[0]
'j'
name[1:3]
'as'
和其他數(shù)據(jù)結(jié)構(gòu)叨粘,如列表猾编、元組一樣,字符串的索引同樣從 0 開(kāi)始升敲,index=0 表示第一個(gè)元素(字符)答倡,[index:index+2]則表示第 index 個(gè)元素到 index+1 個(gè)元素組成的子字符串。
遍歷字符串同樣很簡(jiǎn)單驴党,相當(dāng)于遍歷字符串中的每個(gè)字符瘪撇。
for char in name:
print(char)
j
a
s
o
n
特別要注意,Python 的字符串是不可變的(immutable)港庄。因此倔既,用下面的操作,來(lái)改變一個(gè)字符串內(nèi)部的字符是錯(cuò)誤的攘轩,不允許的叉存。
s = 'hello'
s[0] = 'H'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
Python 中字符串的改變,通常只能通過(guò)創(chuàng)建新的字符串來(lái)完成度帮。比如上述例子中歼捏,想把'hello'的第一個(gè)字符'h',改為大寫(xiě)的'H'笨篷,我們可以采用下面的做法:
s = 'H' + s[1:]
s = s.replace('h', 'H')
- 第一種方法瞳秽,是直接用大寫(xiě)的'H',通過(guò)加號(hào)'+'操作符率翅,與原字符串切片操作的子字符串拼接而成新的字符串练俐。
- 第二種方法,是直接掃描原字符串冕臭,把小寫(xiě)的'h'替換成大寫(xiě)的'H'腺晾,得到新的字符串燕锥。
你可能了解到,在其他語(yǔ)言中悯蝉,如 Java归形,有可變的字符串類(lèi)型,比如 StringBuilder鼻由,每次添加暇榴、改變或刪除字符(串),無(wú)需創(chuàng)建新的字符串蕉世,時(shí)間復(fù)雜度僅為 O(1)蔼紧。這樣就大大提高了程序的運(yùn)行效率。
但可惜的是狠轻,Python 中并沒(méi)有相關(guān)的數(shù)據(jù)類(lèi)型奸例,我們還是得老老實(shí)實(shí)創(chuàng)建新的字符串。因此哈误,每次想要改變字符串哩至,往往需要 O(n) 的時(shí)間復(fù)雜度躏嚎,其中蜜自,n 為新字符串的長(zhǎng)度。
你可能注意到了卢佣,上述例子的說(shuō)明中重荠,我用的是“往往”、“通承椴瑁”這樣的字眼戈鲁,并沒(méi)有說(shuō)“一定”。這是為什么呢嘹叫?顯然婆殿,隨著版本的更新,Python 也越來(lái)越聰明罩扇,性能優(yōu)化得越來(lái)越好了婆芦。
這里,我著重講解一下喂饥,使用加法操作符'+='的字符串拼接方法消约。因?yàn)樗且粋€(gè)例外,打破了字符串不可變的特性员帮。操作方法如下所示:
str1 += str2 # 表示str1 = str1 + str2
我們來(lái)看下面這個(gè)例子:
s = ''
for n in range(0, 100000):
s += str(n)
你覺(jué)得這個(gè)例子的時(shí)間復(fù)雜度是多少呢或粮?
每次循環(huán),似乎都得創(chuàng)建一個(gè)新的字符串捞高;而每次創(chuàng)建一個(gè)新的字符串氯材,都需要 O(n) 的時(shí)間復(fù)雜度渣锦。因此,總的時(shí)間復(fù)雜度就為 O(1) + O(2) + … + O(n) = O(n^2)氢哮。這樣到底對(duì)不對(duì)呢泡挺?
乍一看,這樣分析確實(shí)很有道理命浴,但是必須說(shuō)明娄猫,這個(gè)結(jié)論只適用于老版本的 Python 了。自從 Python2.5 開(kāi)始生闲,每次處理字符串的拼接操作時(shí)(str1 += str2)媳溺,Python 首先會(huì)檢測(cè) str1 還有沒(méi)有其他的引用。如果沒(méi)有的話碍讯,就會(huì)嘗試原地?cái)U(kuò)充字符串 buffer 的大小悬蔽,而不是重新分配一塊內(nèi)存來(lái)創(chuàng)建新的字符串并拷貝。這樣的話捉兴,上述例子中的時(shí)間復(fù)雜度就僅為 O(n) 了蝎困。
因此,以后你在寫(xiě)程序遇到字符串拼接時(shí)倍啥,如果使用’+='更方便禾乘,就放心地去用吧,不用過(guò)分擔(dān)心效率問(wèn)題了虽缕。
另外始藕,對(duì)于字符串拼接問(wèn)題,除了使用加法操作符氮趋,我們還可以使用字符串內(nèi)置的 join 函數(shù)伍派。string.join(iterable),表示把每個(gè)元素都按照指定的格式連接起來(lái)剩胁。
l = []
for n in range(0, 100000):
l.append(str(n))
l = ' '.join(l)
由于列表的 append 操作是 O(1) 復(fù)雜度诉植,字符串同理。因此昵观,這個(gè)含有 for 循環(huán)例子的時(shí)間復(fù)雜度為 n*O(1)=O(n)晾腔。
接下來(lái),我們看一下字符串的分割函數(shù) split()索昂。string.split(separator)建车,表示把字符串按照 separator 分割成子字符串,并返回一個(gè)分割后子字符串組合的列表椒惨。它常常應(yīng)用于對(duì)數(shù)據(jù)的解析處理缤至,比如我們讀取了某個(gè)文件的路徑,想要調(diào)用數(shù)據(jù)庫(kù)的 API,去讀取對(duì)應(yīng)的數(shù)據(jù)领斥,我們通常會(huì)寫(xiě)成下面這樣:
def query_data(namespace, table):
"""
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)
此外嫉到,常見(jiàn)的函數(shù)還有:
- string.strip(str),表示去掉首尾的 str 字符串月洛;
- string.lstrip(str)何恶,表示只去掉開(kāi)頭的 str 字符串;
- string.rstrip(str)嚼黔,表示只去掉尾部的 str 字符串细层。
這些在數(shù)據(jù)的解析處理中同樣很常見(jiàn)。比如很多時(shí)候唬涧,從文件讀進(jìn)來(lái)的字符串中疫赎,開(kāi)頭和結(jié)尾都含有空字符,我們需要去掉它們碎节,就可以用 strip() 函數(shù):
s = ' my name is jason '
s.strip()
'my name is jason'
當(dāng)然捧搞,Python 中字符串還有很多常用操作,比如狮荔,string.find(sub, start, end)胎撇,表示從 start 到 end 查找字符串中子字符串 sub 的位置等等。這里殖氏,我只強(qiáng)調(diào)了最常用并且容易出錯(cuò)的幾個(gè)函數(shù)晚树,其他內(nèi)容你可以自行查找相應(yīng)的文檔、范例加以了解受葛,我就不一一贅述了题涨。
字符串的格式化
最后,我們一起來(lái)看看字符串的格式化总滩。什么是字符串的格式化呢?
通常巡雨,我們使用一個(gè)字符串作為模板闰渔,模板中會(huì)有格式符。這些格式符為后續(xù)真實(shí)值預(yù)留位置铐望,以呈現(xiàn)出真實(shí)值應(yīng)該呈現(xiàn)的格式冈涧。字符串的格式化,通常會(huì)用在程序的輸出正蛙、logging 等場(chǎng)景督弓。
舉一個(gè)常見(jiàn)的例子。比如我們有一個(gè)任務(wù)乒验,給定一個(gè)用戶的 userid愚隧,要去數(shù)據(jù)庫(kù)中查詢?cè)撚脩舻囊恍┬畔ⅲ⒎祷囟腿6绻麛?shù)據(jù)庫(kù)中沒(méi)有此人的信息狂塘,我們通常會(huì)記錄下來(lái)录煤,這樣有利于往后的日志分析,或者是線上 bug 的調(diào)試等等荞胡。
我們通常會(huì)用下面的方法來(lái)表示:
print('no data available for person with id: {}, name: {}'.format(id, name))
其中的 string.format()妈踊,就是所謂的格式化函數(shù);而大括號(hào){}就是所謂的格式符泪漂,用來(lái)為后面的真實(shí)值——變量 name 預(yù)留位置廊营。如果id = '123'、name='jason'萝勤,那么輸出便是:
'no data available for person with id: 123, name: jason'
這樣看來(lái)赘风,是不是非常簡(jiǎn)單呢?
不過(guò)要注意纵刘,string.format() 是最新的字符串格式函數(shù)與規(guī)范邀窃。自然,我們還有其他的表示方法假哎,比如在 Python 之前版本中瞬捕,字符串格式化通常用 % 來(lái)表示,那么上述的例子舵抹,就可以寫(xiě)成下面這樣:
print('no data available for person with id: %s, name: %s' % (id, name))
其中 %s 表示字符串型肪虎,%d 表示整型等等,這些屬于常識(shí)惧蛹,你應(yīng)該都了解扇救。
當(dāng)然,現(xiàn)在你寫(xiě)程序時(shí)香嗓,我還是推薦使用 format 函數(shù)迅腔,畢竟這是最新規(guī)范,也是官方文檔推薦的規(guī)范靠娱。
也許有人會(huì)問(wèn)沧烈,為什么非要使用格式化函數(shù),上述例子用字符串的拼接不也能完成嗎像云?沒(méi)錯(cuò)锌雀,在很多情況下,字符串拼接確實(shí)能滿足格式化函數(shù)的需求迅诬。但是使用格式化函數(shù)腋逆,更加清晰、易讀侈贷,并且更加規(guī)范惩歉,不易出錯(cuò)。
總結(jié)
這節(jié)課,我們主要學(xué)習(xí)了 Python 字符串的一些基本知識(shí)和常用操作柬泽,并且結(jié)合具體的例子與場(chǎng)景加以說(shuō)明慎菲,特別需要注意下面幾點(diǎn)。
- Python 中字符串使用單引號(hào)锨并、雙引號(hào)或三引號(hào)表示露该,三者意義相同,并沒(méi)有什么區(qū)別第煮。其中解幼,三引號(hào)的字符串通常用在多行字符串的場(chǎng)景。
- Python 中字符串是不可變的(前面所講的新版本 Python 中拼接操作’+='是個(gè)例外)包警。因此撵摆,隨意改變字符串中字符的值,是不被允許的害晦。
- Python 新版本(2.5+)中特铝,字符串的拼接變得比以前高效了許多,你可以放心使用壹瘟。
- Python 中字符串的格式化(string.format)常常用在輸出鲫剿、日志的記錄等場(chǎng)景。