Fluent Python 筆記(三):高效操作序列

目錄:

Fluent Python 筆記(一):數據模型

Fluent Python 筆記(二):序列基礎

Fluent Python 筆記(三):高效操作序列

Fluent Python 筆記(四):字典和集合

Fluent Python 筆記(五):把函數當做對象

本篇主要講序列的通用操作,如切片,運算符。以及序列的排序,搜索。還有一些遇到性能瓶頸時,用於取代 list 的數據結構。

切片(Slice)

切片是序列中非常常用的操作,基本格式是 seq[start:stop:step],其中step及之前的冒號均可以省略,表示 step 為1。start和stop也都可以省略,但是第一個冒號不能省略。start省略表示從頭開始,stop省略表示切到尾部。

切片操作適用於幾乎所有序列類型,如 list, tuple, str 等等。

示例:

>>> l = [10, 20, 30, 40, 50, 60]n>>> l[:2] # split at 2n[10, 20]n>>> l[2:]n[30, 40, 50, 60]n>>> l[2:4]n[30, 40]n

Python 中表示範圍一般使用前閉後開 [start, stop),根據這兩個單詞也比較容易記憶。

切片的長度,自然也就是 stop - start。

切片對象:

符號 a:b:c 只有在 [] 中作為索引使用時才有意義,產生了一個切片對象:slice(a, b, c)。

在計算 seq[start:stop:step]時,Python 調用 seq.__getitem__(slice(start, stop, step))。

切片對象是可以命名的(實際上 Python 中的賦值都是把名稱綁定到對象上):

>>> MIDDLE = slice(2:4)n>>> l[MIDDLE]n[30, 40]n

多維切片:

很少會用到,但需要記住的是:

計算 a[i, j] 時,Python 調用 a.__getitem__((i, j))。

切片賦值:

我們可以把可變序列中的某個條目替換為其他值,當然也可以把其中的一個切片對應的部分替換為另一個序列,長度可以不同。也可以把這部分刪除。

示例:

>>> l = list(range(10))n>>> ln[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]n>>> l[2:5] = [20, 30]n>>> ln[0, 1, 20, 30, 5, 6, 7, 8, 9]n>>> del l[5:7]n>>> ln[0, 1, 20, 30, 5, 8, 9]n>>> l[3::2] = [11, 22]n>>> ln[0, 1, 20, 11, 5, 22, 9]n>>> l[2:5] = 100nTraceback (most recent call last):nFile "<stdin>", line 1, in <module>nTypeError: can only assign an iterablen>>> l[2:5] = [100]n>>> ln[0, 1, 100, 22, 9]n

需要注意:

  • 不可變序列不能這樣賦值。(這是很顯然的)
  • 給切片賦值的時候,只能用序列。(不能用單個條目)

序列的運算符操作

加法示例:

>>> [1, 2] + [3, 4, 5]n[1, 2, 3, 4, 5]n

加號兩邊的序列類型必須相同。

乘法示例:

>>> l = [1, 2, 3]n>>> l * 5n[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]n>>> 5 * abcdnabcdabcdabcdabcdabcdn

加法和乘法都是創建一個新的序列,之前的序列都不會修改。

複合賦值:

在執行 += 的時候, Python 會首先檢查 __iadd__ 函數有沒有實現,如果沒有,則 fall back 到 __add__ 。

執行 a += b 後,對於可變序列,a 的地址不會改變,對於不可變序列,a 的地址會改變。

排序

對於 list 來說,有兩種排序方式:

  • list.sort

對一個 list 就地排序,並返回 None 來提醒我們沒有創建新的 list

(這是一個重要的 API 慣例:就地改變對象的函數和方法應該返回 None,從而明確地告訴調用者對象本身改變了,而不是創建了新的對象)

示例:

>>> fruits = [grape, raspberry, apple, banana]n>>> fruits.sort()n>>> fruitsn[apple, banana, grape, raspberry]n

  • sorted 函數

應用於一個列表上,併產生一個新的,已排序的列表,不改變之前的列表。

管理有序序列

在經過上面的方式排序以後,常用的操作有查找和插入。

這兩個操作都使用 bisect 模塊。

  • 使用 bisect.bisect_left 和 bisect.bisect_right 進行搜索

這兩個函數都可以在一個有序序列中搜索 target。

  • 使用 bisect.insort 進行插入操作

排序是比較昂貴的操作,如果已經有了排好序的序列,可以用這個函數保持有序。

(這兩個函數具體使用的時候查閱文檔,常用的話就記住,不常用就了解下好了)

什麼時候不該使用 list ?

由於 list 是一個非常方便的結構,寫程序時不由自主就會首先考慮它,大部分情況下是沒什麼問題的,然而如果遇到了性能瓶頸,就需要考慮使用其他數據結構來替代 list。

  • 如果存的全是數字,那麼使用 array.array

如果需要大量操作數字,請使用 memoryview。

如果還不夠,請使用科學計算庫 NumPy 和 SciPy。

  • 如果經常從兩端添加刪除元素,請使用 collections.deque

deque 是一個線程安全的雙端隊列,設計用來快速從兩端插入刪除。

可以設定最大長度,插入的時候如果超出了最大長度,就從對面忽略元素。

deque 實現了大部分 list 的方法,並添加了一些如 popleft 和 rotate。

append 和 popleft 都是原子操作,所以 deque 作為 LIFO 隊列在多線程應用中是安全的,不需要加鎖。

  • 如果有大量的包含檢測(if item in my_collection),使用集合更好,集合為包含檢測做了優化,保證極高的速度進行成員檢測,當然,需要注意的是集合是無序的。

(下一篇會介紹字典和集合常用操作及底層實現)


推薦閱讀:

Mixin是什麼概念?
Python數據分析及可視化實例之可視化圖表應用簡介
win8環境下python3.4怎麼樣配置才能把scrapy安裝成功?
python做音頻節奏識別(beat detector)有沒有現成的庫 (js也行)?
python多進程進程間通信疑問,求大神指教?(主進程獲取不到子進程變數)

TAG:Python | Python教程 |