深入淺出字符串

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é)成了下面這張表格看锉。


b7a296ab8d26664e03a076fa50d5b152.png

為了方便你理解姿锭,我舉一個(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)景。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載稻轨,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者灵莲。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市殴俱,隨后出現(xiàn)的幾起案子政冻,更是在濱河造成了極大的恐慌,老刑警劉巖线欲,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件明场,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡询筏,警方通過(guò)查閱死者的電腦和手機(jī)榕堰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嫌套,“玉大人,你說(shuō)我怎么就攤上這事圾旨□馓郑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵砍的,是天一觀的道長(zhǎng)痹筛。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么帚稠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任谣旁,我火速辦了婚禮,結(jié)果婚禮上滋早,老公的妹妹穿的比我還像新娘榄审。我一直安慰自己,他們只是感情好杆麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布搁进。 她就那樣靜靜地躺著,像睡著了一般昔头。 火紅的嫁衣襯著肌膚如雪饼问。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天揭斧,我揣著相機(jī)與錄音莱革,去河邊找鬼。 笑死讹开,一個(gè)胖子當(dāng)著我的面吹牛盅视,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萧吠,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼左冬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了纸型?” 一聲冷哼從身側(cè)響起拇砰,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狰腌,沒(méi)想到半個(gè)月后除破,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琼腔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瑰枫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丹莲。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡光坝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甥材,到底是詐尸還是另有隱情盯另,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布洲赵,位于F島的核電站鸳惯,受9級(jí)特大地震影響商蕴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芝发,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一绪商、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辅鲸,春花似錦格郁、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绵患,卻和暖如春雾叭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背落蝙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工织狐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筏勒。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓移迫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親管行。 傳聞我的和親對(duì)象是個(gè)殘疾皇子厨埋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351