深入淺出字符串-day3

深入淺出字符串

寫在前面

滴滴~~ 打卡第三天!

Python 的程序中充滿了字符串(string),在平常閱讀代碼時也屢見不鮮。字符串同樣是 Python 中很常見的一種數據類型,比如日誌的打印、程序中函數的註釋、數據庫的訪問、變量的基本操作等等,都用到了字符串。當然,我相信你本身對字符串已經有所瞭解。今天,主要一起回顧一下字符串的常用操作,並對其中的一些小 tricks 詳細地加以解釋。

字符串基礎

什麼是字符串呢?字符串是由獨立字符組成的一個序列,通常包含在單引號(’’)雙引號("")或者三引號之中(’’’ ‘’'或""" “”",兩者一樣),比如下面幾種寫法。


name = 'jason'
city = 'beijing'
text = "welcome to jike shijian"

這裏定義了 name、city 和 text 三個變量,都是字符串類型。我們知道,Python 中單引號、雙引號和三引號的字符串是一模一樣的,沒有區別,比如下面這個例子中的 s1、s2、s3 完全一樣。


s1 = 'hello'
s2 = "hello"
s3 = """hello"""
s1 == s2 == s3
True

Python 同時支持這三種表達方式,很重要的一個原因就是,這樣方便你在字符串中,內嵌帶引號的字符串。比如:

“I’m a student”

Python 的三引號字符串,則主要應用於多行字符串的情境,比如函數的註釋等等。


def calculate_similarity(item1, item2):
    """
    Calculate similarity between two items
    Args:
        item1: 1st item
        item2: 2nd item
    Returns:
      similarity score between item1 and item2
    """

同時,Python 也支持轉義字符。所謂的轉義字符,就是用反斜槓開頭的字符串,來表示一些特定意義的字符。我把常見的的轉義字符,總結成了下面這張表格。

在這裏插入圖片描述

爲了方便你理解,我舉一個例子來說明。

s = ‘a\nb\tc’
print(s)
a
b c

這段代碼中的’\n’,表示一個字符——換行符;’\t’也表示一個字符——橫向製表符。所以,最後打印出來的輸出,就是字符 a,換行,字符 b,然後製表符,最後打印字符 c。不過要注意,雖然最後打印的輸出橫跨了兩行,但是整個字符串 s 仍然只有 5 個元素。

len(s)
5

在轉義字符的應用中,最常見的就是換行符’\n’的使用。比如文件讀取,如果我們一行行地讀取,那麼每一行字符串的末尾,都會包含換行符’\n’。而最後做數據處理時,我們往往會丟掉每一行的換行符。

常用操作

看完了字符串的基本原理,下面我們一起來看看字符串的常用操作。你可以把字符串想象成一個由單個字符組成的數組,所以,Python 的字符串同樣支持索引,切片和遍歷等等操作。


name = 'jason'
name[0]
'j'
name[1:3]
'as'

和其他數據結構,如列表、元組一樣,字符串的索引同樣從 0 開始,index=0 表示第一個元素(字符),[index:index+2]則表示第 index 個元素到 index+1 個元素組成的子字符串。

遍歷字符串同樣很簡單,相當於遍歷字符串中的每個字符。


for char in name:
    print(char)   
j
a
s
o
n

特別要注意,Python 的字符串是不可變的(immutable)。因此,用下面的操作,來改變一個字符串內部的字符是錯誤的,不允許的。


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 中字符串的改變,通常只能通過創建新的字符串來完成。比如上述例子中,想把’hello’的第一個字符’h’,改爲大寫的’H’,我們可以採用下面的做法:


s = 'H' + s[1:]
s = s.replace('h', 'H')
  • 第一種方法,是直接用大寫的’H’,通過加號’+'操作符,與原字符串切片操作的子字符串拼接而成新的字符串。
  • 第二種方法,是直接掃描原字符串,把小寫的’h’替換成大寫的’H’,得到新的字符串。

你可能瞭解到,在其他語言中,如 Java,有可變的字符串類型,比如 StringBuilder,每次添加、改變或刪除字符(串),無需創建新的字符串,時間複雜度僅爲 O(1)。這樣就大大提高了程序的運行效率。

但可惜的是,Python 中並沒有相關的數據類型,我們還是得老老實實創建新的字符串。因此,每次想要改變字符串,往往需要 O(n) 的時間複雜度,其中,n 爲新字符串的長度。

你可能注意到了,上述例子的說明中,我用的是“往往”、“通常”這樣的字眼,並沒有說“一定”。這是爲什麼呢?顯然,隨着版本的更新,Python 也越來越聰明,性能優化得越來越好了。

這裏,我着重講解一下,使用加法操作符’+='的字符串拼接方法。因爲它是一個例外,打破了字符串不可變的特性。

操作方法如下所示:

str1 += str2 # 表示str1 = str1 + str2

我們來看下面這個例子:


s = ''
for n in range(0, 100000):
    s += str(n)

你覺得這個例子的時間複雜度是多少呢?

每次循環,似乎都得創建一個新的字符串;而每次創建一個新的字符串,都需要 O(n) 的時間複雜度。因此,總的時間複雜度就爲 O(1) + O(2) + … + O(n) = O(n^2)。這樣到底對不對呢?

乍一看,這樣分析確實很有道理,但是必須說明,這個結論只適用於老版本的 Python 了。自從 Python2.5 開始,每次處理字符串的拼接操作時(str1 += str2),Python 首先會檢測 str1 還有沒有其他的引用。如果沒有的話,就會嘗試原地擴充字符串 buffer 的大小,而不是重新分配一塊內存來創建新的字符串並拷貝。這樣的話,上述例子中的時間複雜度就僅爲 O(n) 了。

因此,以後你在寫程序遇到字符串拼接時,如果使用’+='更方便,就放心地去用吧,不用過分擔心效率問題了。

另外,對於字符串拼接問題,除了使用加法操作符,我們還可以使用字符串內置的 join 函數。string.join(iterable),表示把每個元素都按照指定的格式連接起來。


l = []
for n in range(0, 100000):
    l.append(str(n))
l = ' '.join(l) 

由於列表的 append 操作是 O(1) 複雜度,字符串同理。因此,這個含有 for 循環例子的時間複雜度爲 n*O(1)=O(n)。

接下來,我們看一下字符串的分割函數 split()。string.split(separator),表示把字符串按照 separator 分割成子字符串,並返回一個分割後子字符串組合的列表。它常常應用於對數據的解析處理,比如我們讀取了某個文件的路徑,想要調用數據庫的 API,去讀取對應的數據,我們通常會寫成下面這樣:


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) 

此外,常見的函數還有:

  • string.strip(str),表示去掉首尾的 str 字符串;
  • string.lstrip(str),表示只去掉開頭的 str 字符串;
  • string.rstrip(str),表示只去掉尾部的 str 字符串。

這些在數據的解析處理中同樣很常見。比如很多時候,從文件讀進來的字符串中,開頭和結尾都含有空字符,我們需要去掉它們,就可以用 strip() 函數:s = ’ my name is jason ’

s = ’ my name is jason ’
s.strip()
‘my name is jason’

當然,Python 中字符串還有很多常用操作,比如,string.find(sub, start, end),表示從 start 到 end 查找字符串中子字符串 sub 的位置等等。這裏,我只強調了最常用並且容易出錯的幾個函數,其他內容你可以自行查找相應的文檔、範例加以瞭解,我就不一一贅述了。

字符串格式化

最後,我們一起來看看字符串的格式化。什麼是字符串的格式化呢?

通常,我們使用一個字符串作爲模板,模板中會有格式符。這些格式符爲後續真實值預留位置,以呈現出真實值應該呈現的格式。字符串的格式化,通常會用在程序的輸出、logging 等場景。

舉一個常見的例子。比如我們有一個任務,給定一個用戶的 userid,要去數據庫中查詢該用戶的一些信息,並返回。而如果數據庫中沒有此人的信息,我們通常會記錄下來,這樣有利於往後的日誌分析,或者是線上 bug 的調試等等。

我們通常會用下面的方法來表示:

print(‘no data available for person with id: {}, name: {}’.format(id, name))

其中的 string.format(),就是所謂的格式化函數;而大括號{}就是所謂的格式符,用來爲後面的真實值——變量 name 預留位置。如果id = ‘123’、name=‘jason’,那麼輸出便是:

‘no data available for person with id: 123, name: jason’

這樣看來,是不是非常簡單呢?

不過要注意,string.format() 是最新的字符串格式函數與規範。自然,我們還有其他的表示方法,比如在 Python 之前版本中,字符串格式化通常用 % 來表示,那麼上述的例子,就可以寫成下面這樣:

print(‘no data available for person with id: %s, name: %s’ % (id, name))

其中 %s 表示字符串型,%d 表示整型等等,這些屬於常識,你應該都瞭解。

當然,現在你寫程序時,我還是推薦使用 format 函數,畢竟這是最新規範,也是官方文檔推薦的規範。

也許有人會問,爲什麼非要使用格式化函數,上述例子用字符串的拼接不也能完成嗎?沒錯,在很多情況下,字符串拼接確實能滿足格式化函數的需求。但是使用格式化函數,更加清晰、易讀,並且更加規範,不易出錯。

寫在後面

哈哈哈,第三天了,繼續堅持!

s 表示字符串型,%d 表示整型等等,這些屬於常識,你應該都瞭解。

當然,現在你寫程序時,我還是推薦使用 format 函數,畢竟這是最新規範,也是官方文檔推薦的規範。

也許有人會問,爲什麼非要使用格式化函數,上述例子用字符串的拼接不也能完成嗎?沒錯,在很多情況下,字符串拼接確實能滿足格式化函數的需求。但是使用格式化函數,更加清晰、易讀,並且更加規範,不易出錯。

寫在後面

哈哈哈,第三天了,繼續堅持!

大家多多支持哈,自我感覺良好,寫的挺良心的。

站住,別跑!點了贊再走!

CSDN:禪墨雲
知乎: 禪墨雲
公衆號:興趣路人甲

這裏是引用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章