4300 字Python列表使用總結,用心!

我的完整施工計劃

已完成專題:

1.我的施工計劃

2.數字專題

3.字符串專題

今天列表專題的目錄如下:

列表基礎

    • 1 創建列表

    • 2 訪問元素

    • 3 添加元素

    • 4 刪除元素

    • 5 list 與 in

    • 6 list 與數字

    • 7 列表生成式

  • 列表進階

    • 8 其他常用API

    • 9 列表實現棧

    • 10 列表包含自身

    • 11 插入元素性能分析

    • 12 深淺拷貝

    • 13 列表可變性

列表基礎

1 創建列表

列表是一個容器,使用一對中括號[]創建一個列表。

創建一個空列表:

a = [] # 空列表

創建一個含有 5 個整型元素的列表a

a = [3,7,4,2,6]

列表與我們熟知的數組很相似,但又有很大區別。

一般數組內的元素要求同一類型,但是列表內可含有各種不同類型,包括再嵌套列表。

如下,列表a包含三種類型:整形,字符串,浮點型:

如下列表a嵌套兩個列表:

2 訪問元素

列表訪問主要包括兩種:索引和切片。

如下,訪問列表a可通過我們所熟知的正向索引,注意從0開始;

也可通過Python特有的負向索引,

即從列表最後一個元素往前訪問,此時索引依次被標記爲-1,-2,...,-5 ,注意從-1開始。

除了以上通過索引訪問單個元素方式外,

還有非常像matlab的切片訪問方式,這是一次訪問多個元素的方法。

切片訪問的最基本結構:中間添加一個冒號。

如下切片,能一次實現訪問索引爲1到4,不包括4的序列:

In [1]: a=[3,7,4,2,6]

In [2]: a[1:4]
Out[2]: [7, 4, 2]

Python支持負索引,能帶來很多便利。比如能很方便的獲取最後三個元素:

In [1]: a=[3,7,4,2,6]

In [3]: a[-3:]
Out[3]: [4, 2, 6]

除了使用一個冒號得到連續切片外,

使用兩個冒號獲取帶間隔的序列元素,兩個冒號後的數字就是間隔長度:

In [1]: a=[3,7,4,2,6]

In [7]: a[::2] # 得到切片間隔爲2
Out[7]: [3, 4, 6]

其實最全的切片結構:start:stop:interval,如下所示,獲得切片爲:索引從15間隔爲2:

In [6]: a=[3,7,4,2,6]

In [7]: a[1:5:2]
Out[7]: [7, 2]

3 添加元素

列表與數組的另一個很大不同,使用數組前,需要知道數組長度,便於從系統中申請內存。

但是,列表卻不需要預先設置元素長度。

它支持任意的動態添加元素,完全不用操心列表長短。

它會隨着數組增加或刪除而動態的調整列表大小。

這與數據結構中的線性表或向量很相似。

添加元素通常有兩類場景。

append一次添加1個元素,insert在指定位置添加元素:

In [8]: a=[3,7,4,2,6]
In [9]: a.append(1) # append默認在列表尾部添加元素
In [10]: a
Out[10]: [3, 7, 4, 2, 6, 1]

In [11]: a.insert(2,5) # insert 在索引2處添加元素5
In [12]: a
Out[12]: [3, 7, 5, 4, 2, 6, 1]

extend或直接使用+實現一次添加多個元素:

In [13]: a.extend([0,10])# 一次就地添加0,10兩個元素
In [14]: a
Out[14]: [3, 7, 5, 4, 2, 6, 1, 0, 10]

In [15]: b = a+[11,21] # + 不是就地添加,而是重新創建一個新的列表
In [16]: b
Out[16]: [3, 7, 5, 4, 2, 6, 1, 0, 10, 11, 21]

這裏面有一個重要細節,不知大家平時注意到嗎。

extend 方法實現批量添加元素時未創建一個新的列表,而是直接添加在原列表中,這被稱爲in-place,就地。而b=a+list對象實際是創建一個新的列表對象,所以不是就地批量添加元素。

但是,a+=一個列表對象+=操作符則就會自動調用extend方法進行合併運算。大家注意這些微妙的區別,不同場景選用不同的API,以此高效節省內存。

4 刪除元素

刪除元素的方法有三種:remove,pop,del.

remove直接刪除元素,若被刪除元素在列表內重複出現多次,則只刪除第一次:

In [17]: a=[1,2,3,2,4,2]
In [18]: a.remove(2)
In [19]: a
Out[19]: [1, 3, 2, 4, 2]

pop方法若不帶參數默認刪除列表最後一個元素;若帶參數則刪除此參數代表的索引處的元素:

In[19]: a = [1, 3, 2, 4, 2]
In [20]: a.pop() # 刪除最後一個元素
Out[20]: 2
In [21]: a
Out[21]: [1, 3, 2, 4]

In [22]: a.pop(1) # 刪除索引等於1的元素
Out[22]: 3
In [23]: a
Out[23]: [1, 2, 4]

delpop相似,刪除指定索引處的元素:

In [24]: a = [1, 2, 4]
In [25]: del a[1:] # 刪除索引1到最後的切片序列
In [26]: a
Out[26]: [1]

5 list 與 in

列表是可迭代的,除了使用類似c語言的索引遍歷外,還支持for item in alist這種直接遍歷元素的方法:

In [28]: a = [3,7,4,2,6]
In [29]: for item in a:
    ...:     print(item)
3
7
4
2
6

in 與可迭代容器的結合,還用於判斷某個元素是否屬於此列表:

In [28]: a = [3,7,4,2,6]
In [30]: 4 in a
Out[30]: True

In [31]: 5 in a
Out[31]: False

6 list 與數字

內置的list與數字結合,實現元素的複製,如下所示:

In [32]: ['Hi!'] * 4
Out[32]: ['Hi!', 'Hi!', 'Hi!', 'Hi!']

表面上這種操作太方便,實際確實也很方便,比如我想快速打印20個-,只需下面一行代碼:

In [33]: '-'*20
Out[33]: '--------------------'

使用列表與數字相乘構建二維列表,然後第一個元素賦值爲[1,2],第二個元素賦值爲[3,4],第三個元素爲[5]

In [34]: a = [[]] * 3
In [35]: a[0]=[1,2]
In [36]: a[1]=[3,4]
In [37]: a[2]=[5]
In [38]: a
Out[38]: [[1, 2], [3, 4], [5]]

7 列表生成式

列表生成式是創建列表的一個方法,它與使用append等API創建列表相比,書寫更加簡潔。

使用列表生成式創建1到50的所有奇數列表:

a=[i for i in range(50) if i&1]

列表進階

8 其他常用API

除了上面提到的方法外,列表封裝的其他方法還包括如下:

clear,index,count,sort,reverse,copy

clear 用於清空列表內的所有元素index 用於查找裏面某個元素的索引:

In [4]: a=[1,3,7]

In [5]: a.index(7)
Out[5]: 2

count 用於統計某元素的出現次數:

In [6]: a=[1,2,3,2,2,5]

In [7]: a.count(2) # 元素2出現3次
Out[7]: 3

sort 用於元素排序,其中參數key定製排序規則。如下列表,其元素爲元祖,根據元祖的第二個值由小到大排序:

In [8]: a=[(3,1),(4,1),(1,3),(5,4),(9,-10)]

In [9]: a.sort(key=lambda x:x[1])

In [10]: a
Out[10]: [(9, -10), (3, 1), (4, 1), (1, 3), (5, 4)]

reverse 完成列表反轉:

In [15]: a=[1,3,-2]

In [16]: a.reverse()

In [17]: a
Out[17]: [-2, 3, 1]

copy 方法在下面講深淺拷貝時會詳細展開。

9 列表實現棧

列表封裝的這些方法,實現這個常用的數據結構比較容易。棧是一種只能在列表一端進出的特殊列表,pop方法正好完美實現:

In [23]: stack=[1,3,5]

In [24]: stack.append(0) # push元素0到尾端,不需要指定索引

In [25]: stack
Out[25]: [1, 3, 5, 0]

In [26]: stack.pop() # pop元素,不需指定索引,此時移出尾端元素
Out[26]: 0

In [27]: stack
Out[27]: [1, 3, 5]

由此可見Python的列表當做棧用,完全沒有問題,push 和 pop 操作的時間複雜度都爲 O(1)

但是使用列表模擬隊列就不那麼高效了,需要藉助Python的collections模塊中的雙端隊列deque實現。

10 列表包含自身

列表的賦值操作,有一個非常有意思的問題,大家不妨耐心看一下。

In [1]: a=[1,3,5]

In [2]: a[1]=a  # 列表內元素指向自身

這樣相當於創建了一個引用自身的結構。

打印結果顯示是這樣的:

In [3]: a
Out[3]: [1, [...], 5]

中間省略號表示無限循環,這種賦值操作導致無限循環,這是爲什麼?下面分析下原因。

執行 a = [1,3,5] 的時候,Python 做的事情是首先創建一個列表對象 [1, 3, 5],然後給它貼上名爲a的標籤。

執行 a[1] = a 的時候,Python 做的事情則是把列表對象的第二個元素指向a所引用的列表對象本身。

執行完畢後,a標籤還是指向原來的那個對象,只不過那個對象的結構發生了變化。

從之前的列表 [1,3,5] 變成了 [1,[...], 5],而這個[...]則是指向原來對象本身的一個引用。

如下圖所示:

可以看到形成一個環路:a[1]--->中間元素--->a[1],所以導致無限循環。

11 插入元素性能分析

與常規數組需要預先指定長度不同,Python 中list不需要指定容器長度,允許我們隨意的添加刪除元素。

但是這種便捷性也會帶來一定副作用,就是插入元素的時間複雜度爲O(n),而不是O(1),因爲insert會導致依次移動插入位置後的所有元素。

爲了加深對插入元素的理解,特意把cpython實現insert元素的操作源碼拿出來。

可以清楚看到insert元素時,插入位置處的元素都會後移一個位置,因此插入元素的時間複雜爲O(n),所以凡是涉及頻繁插入刪除元素的操作,都不太適合用list.

static int
ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
    assert((size_t)n + 1 < PY_SSIZE_T_MAX);
    if (list_resize(self, n+1) < 0)
        return -1;

    if (where < 0) {
        where += n;
        if (where < 0)
            where = 0;
    }
    if (where > n)
        where = n;
    items = self->ob_item;
    //依次移動插入位置後的所有元素
    // O(n) 時間複雜度
    for (i = n; --i >= where; ) 
        items[i+1] = items[i];
    Py_INCREF(v);
    items[where] = v;
    return 0;
}

12 深淺拷貝

list 封裝的copy 方法實現對列表的淺拷貝,淺拷貝只拷貝一層,具體拿例子說:

In [38]: c =[1,3,5]

In [39]: cc = c.copy()

ccc分別指向一片不同內存,示意圖如下:

這樣修改cc的第一個元素,原來c不受影響:

In [40]: cc[0]=10 # 修改cc第一個元素

In [41]: cc
Out[41]: [10, 3, 5]

In [42]: c # 原來 c 不受影響
Out[42]: [1, 3, 5]

但是,如果內嵌一層列表,再使用copy時只拷貝一層:

In [32]: a=[[1,3],[4,2]]

In [33]: ac = a.copy()

In [34]: ac
Out[34]: [[1, 3], [4, 2]]

上面的示意圖清晰的反映出這一點,內嵌的列表並沒有實現拷貝。因此再修改內嵌的元素時,原來的列表也會受到影響。

In [35]: ac[0][0]=10

In [36]: ac
Out[36]: [[10, 3], [4, 2]]

In [37]: a
Out[37]: [[10, 3], [4, 2]]

要想實現深度拷貝,需要使用Python模塊copy中的deepcopy方法。

13 列表可變性

列表是可變的,可變的對象是不可哈希的,不可哈希的對象不能被映射,因此不能被用作字典的鍵。

In [51]: a=[1,3]
In [52]: d={a:'不能被哈希'} #會拋出如下異常

# TypeError: unhashable type: 'list'

但是,有時我們確實需要列表對象作爲鍵,這怎麼辦?

可以將列表轉化爲元祖,元祖是可哈希的,所以能作爲字典的鍵。

總結

以上就是列表專題的所有13個方面總結,目錄如下:

  • 列表基礎

    • 1 創建列表

    • 2 訪問元素

    • 3 添加元素

    • 4 刪除元素

    • 5 list 與 in

    • 6 list 與數字

    • 7 列表生成式

  • 列表進階

    • 8 其他常用API

    • 9 列表實現棧

    • 10 列表包含自身

    • 11 插入元素性能分析

    • 12 深淺拷貝

    • 13 列表可變性

  • 總結

另外,振哥努力擠時間寫原創文章,爲大家爭取來了留言送書機會。

復旦大學邱老師這本書不用我多說,屬於經典必讀書籍。本次留言送1本,7月3日晚上揭曉誰的留言會被送書1本。

不管是這篇專題總結還是送書,振哥都十分用心,只想爲朋友們奉獻一點。這篇文章完成花費我好幾天時間,全文4200多字,多幅圖,全部是個人一字一字寫出來的。原創不易,歡迎三連支持,讓我更有動力堅持寫好下一個專題,完成整個施工。

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