第二章(提煉) 序列構成的數組(三)

一. 對序列使用+和*

         Python 程序員會默認序列是支持 + * 操作的。通常 + 號兩側的序列由相同類型的數據所構成,在拼接的過程中,兩個被操作的序列都不會被修改,Python 會新建一個包含同樣類型數據的序列來作爲拼接的結果。

        如果想要把一個序列複製幾份然後再拼接起來,更快捷的做法是把這個序列乘以一個整數。+ 和 * 都遵循這個規律,不修改原有的操作對象,而是構建一個全新的序列。

        如果在 a * n 這個語句中,序列 a 裏的元素是對其他可變 對象的引用的話,你就需要格外注意了,因爲這個式子的結果可能 會出乎意料。比如,你想用 my_list = [[]] * 3 來初始化一個 由列表組成的列表,但是你得到的列表裏包含的 3 個元素其實是 3 個引用,而且這 3 個引用指向的都是同一個列表。

演示1  建立由列表組成的列表

上述操作等同於(每次迭代中新建了一個list。作爲新的一行添加到board中):

演示2  含有3個指向同一對象的引用的列表是毫無用處的

 

上述錯誤示範等同於(追加同一個對象3次到board列表):

二. 序列的增量賦值

2.1 +=和*=背後的故事

        增量賦值運算符 += *= 的表現取決於它們的第一個操作對象。+= 背後的特殊方法是 __iadd__ (用於“就地加法”)。但是如果一個類沒有實現這個方法的話,Python 會退一步調用 __add__ 。如:在a += b的執行過程中,如果 a 實現了 __iadd__ 方法,就會調用這個方法。同時對可變序列來說,a 會就地改動,就像調用了 a.extend(b) 一樣。但是如果 a 沒有實現 __iadd__ 的話,a += b 這個表達式的效果就變得跟 a = a + b 一樣了:首先計算 a + b,得到一個新的對象,然後賦值給 a。也就是說,在這個表達式中,變量名會不會被關聯到新的對象,完全取決於這個類型有沒有實現 __iadd__ 這個方法。

        總體來講,可變序列一般都實現了 __iadd__ 方法,因此 += 是就地加 法。而不可變序列根本就不支持這個操作,對這個方法的實現也就無從 談起。 上面所說的這些關於 += 的概念也適用於 *=,不同的是,後者相對應的 是 __imul__

演示3  *=在可變和不可變序列上的對比

        對不可變序列進行重複拼接操作的話,效率會很低,因爲每次都有一個新對象,而解釋器需要把原來對象中的元素先複製到新的對象裏,然後再追加新的元素。

        str 是一個例外,因爲對字符串做 += 實在是太普遍了,所以 CPython 對它做了優化。爲 str 初始化內存的時候,程序會爲它留出額外的可擴展空間,因此進行增量操作的時候,並不會涉及複製原有字符串到新位置這類操作。

2.2 一個關於+=的謎題

        上述代碼中,因爲 tuple 不支持對它的元素賦值,所以會拋出 TypeError 異常。但與此同時元組卻被改變了:t= (1, 2, [30, 40, 50, 60])。這是非常罕見的邊界情況,因此我們需要注意以下3點:

  • 不要把可變對象放在元組裏面。
  • 增量賦值不是一個原子操作。我們剛纔也看到了,它雖然拋出了異常,但還是完成了操作。

三. list.sort方法和內置函數sorted

        list.sort 方法會就地排序列表,也就是說不會把原列表複製一份。這也是這個方法的返回值是 None 的原因。在這種情況下返回 None 其實是 Python 的一個慣例:如果一個函數或者方法對對象進行的是就地改動,那它就應該返回 None,好讓調用者知道傳入的參數發生了變動,而且並未產生新的對象。例如,random.shuffle 函數也遵守了這個慣例。(注:用返回 None 來表示就地改動這個慣例有個弊端,那就是調用者無法將其串聯起來。

        與 list.sort 相反的是內置函數 sorted,它會新建一個列表作爲返回值。這個方法可以接受任何形式的可迭代對象作爲參數,甚至包括不可變序列或生成器。而不管 sorted 接受的是怎樣的參數,它最後都會返回一個列表。

        不管是 list.sort 方法還是 sorted 函數,都有兩個可選的關鍵字參數:

  • reverse:如果被設定爲 True,被排序的序列裏的元素會以降序輸出;默認值是 False升序
  • key一個只有一個參數的函數,這個函數會被用在序列裏的每一個元素上,所產生的結果將是排序算法依賴的對比關鍵字。這個參數的默認值是恆等函數(identity function),也就是默認用元素自己的值來排序。

演示4  list.sort()和sorted()的用法

        已排序的序列可以用來進行快速搜索,而標準庫的 bisect 模塊給我們提供了二分查找算法。下一節會詳細講這個函數,順便還會看看bisect.insort 如何讓已排序的序列保持有序。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章