python列表基礎和一些經典使用案例

1. 寫在前面

好久沒有更新python這一塊的內容了, 所以今天整理一塊python的內容。今天整理的內容是python裏面的列表, 作爲在python中非常常見的數據類型, 嘗試用一篇文章來整理其常用的操作,方便以後查看使用。 目前可能不全,以後遇到列表相關的操作都放到這篇文章裏面來。

首先從列表的基礎操作開始, 看一下什麼是python列表, 列表背後的內存組織, 然後介紹列表裏面的常用操作, 像列表的添加, 刪除,切片等。 接下來會介紹點深淺拷貝的知識和可變不可變對象。 最後整理常用的列表使用案例。

這次整理列表, 會從純使用的角度去整理, 關於列表的理論知識和細節知識, 這裏不做整理。

知識框架

  • 列表的基礎操作(添加, 刪除, 切片, 索引, 遍歷等)
  • 深淺拷貝和可變不可變
  • 列表常用的使用小例子

ok, let’s go!

2. 列表的基礎操作

關於list, 它是python中非常重要的一種數據類型, 常用的創建方式會有兩種: []和list(), 下面我們先嚐試創建一個列表:

lst = [1, 3, 5]   # lst就是一個列表
# lst = list((1, 3, 5))

我們看看這行代碼在內存中是什麼樣子的:
在這裏插入圖片描述
是有一塊內存空間存放[1, 3, 5], 然後把這塊內存空間的首地址給了lst。注意,這個地方右邊是開着的, 和(1,3,5)還不一樣, 後者表示元組, 在內存裏面是這樣:
在這裏插入圖片描述
元組也是python中容器型的一種, 和list不同的是, list是一種可變對象, 而元組是一種不可變對象。

2.1 列表的常用操作

創建完列表之後,我們要會使用,這裏介紹一些基本的使用,比如查看列表的元素個數, 遍歷列表看元素和及其類型, 列表的添加, 刪除, 切片,索引。 下面就來看看:

# 創建一個列表
lst = [1, 'xiaoming', 29.5, '17382348927']      # 列表裏面元素類型不一定一樣

print(len(lst))   # len函數返回列表的元素個數, 非常常用

# 列表的索引 由於下標是0-len(st)-1, 所以通過下標就可以訪問某個元素
lst[2]   # 這個就是29.5


# 遍歷列表
for _ in lst:     # 這行代碼就可以遍歷列表查看每個元素的類型
	print(f"{_}的類型爲{type(_)}")

## 結果
1的類型爲<class 'int'>
xiaoming的類型爲<class 'str'>
29.5的類型爲<class 'float'>
17382348927的類型爲<class 'str'>

列表作爲一種可變對象, 我們是可以在裏面添加和刪除元素的, 關於列表的添加, 常用的方式有三種: append, extend, insert。

# append方式從列表的尾部添加元素
lst = [1, 'xiaoming', 29.5, '17382348927']
lst.append('hello')   #[1, 'xiaoming', 29.5, '17382348927', 'hello']
lst.append(['hello', 'world'])  #  #[1, 'xiaoming', 29.5, '17382348927', 'hello', ['hello', 'world']]  
# 上面的第二個是列表裏面又加了個列表元素

# extend也是從列表尾部添加元素, 但是和append又有些區別
lst.extend(['hello', 'world'])  # [1, 'xiaoming', 29.5, '17382348927', 'hello', 'world]  
# 注意看區別, extend的時候,是隻要後面列表裏面的元素, 不是把列表加進去

# insert 在指定位置添加元素
lst.insert(1, 10)
# [1, 10, 'xiaoming', 29.5, '17382348927']

上面的都比較簡單, 做簡單回顧。

關於列表的刪除常用的方式: pop和remove

lst = [1, 'xiaoming', 29.5, '17382348927']

# pop 默認是從尾部刪除元素並返回
lst.pop()   # 這個是'17382348927' 
# 執行了上面這句代碼之後, lst變成了[1, 'xiaoming', 29.5], 當然pop也可以指定位置刪除元素
lst.pop(1) 

# remove()刪除指定元素
lst.remove('xiaoming')

關於索引的切片, 這裏不多說, 簡單演示:

a = [i for i in range(1, 10)]
print(a)
print(a[::-1])        # 列表翻轉  這個很實用
print(a[:-1])
print(a[1:5:2])
print(a[::-3])

## 結果
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1]
[1, 2, 3, 4, 5, 6, 7, 8]
[2, 4]
[9, 6, 3]

3. 關於深淺拷貝和可變不可變

先拋開深淺拷貝, 我們先來看一個列表的例子:

# 列表的創建
lst2 = ['001','2019-11-11',['三文魚','電烤箱']]

這個列表看起來很簡單, 但是內部怎麼組織的呢?
在這裏插入圖片描述
現在就知道了, 列表裏面套列表的時候, 在內存的角度到底是如何組織的。 這時候,我們把裏面那個列表給賦值到一個新的變量上面去:

# 索引
sku = lst2[2]   # ['三文魚', '電烤箱']

這時候內存是這樣子的:
在這裏插入圖片描述
就是sku也指向到了裏面的這個列表中。 這時候, 很明顯,我們嘗試修改sku指向的列表, 那麼lst[2]肯定也會變:

sku.append('烤鴨')   

# 這時候
sku  ['三文魚', '電烤箱', '烤鴨']
lst2  ['001','2019-11-11',['三文魚','電烤箱', '烤鴨']]

也就是這種情況下, 修改sku會導致lst2本身也會改變。 看內部原理圖就非常容易理解, 那麼有沒有一種方式,我改sku的時候,不改變lst2本身呢?

3.1 深淺拷貝

淺拷貝可以幫助我們完成上面的要求, 淺拷貝.copy(), 把lst2[2]進行拷貝一份, 這時候就發現sku變量與lst2[2]指向的對象就不是一個了。

sku_deep = lst2[2].copy()
sku_deep     # ['三文魚', '電烤箱']

雖然內容是一樣, 但看內存:
在這裏插入圖片描述
這時候很顯然, 修改sku_keep就不會影響lst2的內容了。 那麼淺拷貝和深拷貝有啥區別呢?

上面這個例子可能還看不出區別, 但是下面這個例子就非常看出事情:

# 再創建一個新的列表,也是列表套列表
a = [1, 2, [3, 4, 5]]
ac = a.copy()   # ac是a的一個淺拷貝

# 我們改一下ac的值
ac[0] = 10   
print(a[0], ac[0])   # 1 10    確實實現了拷貝, a和ac分開了

# 但是真的分的徹底嗎?  我們改一下ac的值,但這次該裏面那個列表
ac[2][1] = 40
print(a[2][1], ac[2][1]) # 40 40 我們發現了啥?  竟然改ac裏面列表的時候a裏面的列表也跟着變了

上面例子就說明了淺拷貝,並沒有拷貝的那麼徹底, 像這種列表套列表的情況, 淺拷貝僅僅拷貝了第一層而已, 看個圖就明白了:
在這裏插入圖片描述
這個圖很清晰的說明了上面的那兩個變化情況。 原來淺拷貝之後, a[2]和ac[2]依然指向了同一塊空間。 類似於藕斷絲連。 淺拷貝之後, 我弄一塊新內存放外面的列表, 但是列表裏面再有列表的話, 我不管

那麼深拷貝是怎麼回事呢? 可能你也想到了, 那就是斷的徹底。 即使列表裏面套列表, 當我深拷貝之後, 我弄一塊內存放外面的表, 同時如果列表裏面有列表, 我也會弄一塊新內存放裏面的表。看下面的例子和圖:

from copy import deepcopy

a = [1, 2, [3, 4, 5]]
ac = deepcopy(a)
ac[0] = 10
ac[2][1] = 40
print(a[0], ac[0])   # 1 10 
print(ac[2][1], a[2][1])    # 40 4

深拷貝是deepcopy(), 深拷貝之後我們發現a和ac就完全沒有關係了。
在這裏插入圖片描述

3.2 關於可變與不可變

這個話題其實在python那裏嘗試整理了一下, 這裏再用內部圖的方式重新理解一下, 就拿列表和元組來看即可, 看看這個可變和不可變到底啥意思?可變和不可變是一對很微妙的概念

我們知道,列表是可變對象, 那麼這個可變是啥意思呢? 我們建立一個列表看一下:

# 創建一個列表
a = [1, 3, [5, 7], 9, 11, 13]   

上面的a是一個普通列表, 但是又不算一個很普通的列表,因爲裏面還有一個列表, 我們看看這個的內部圖:
在這裏插入圖片描述
那麼什麼是可變呢? 這個意思是a指向的對象本身是可以變得, 比如我們在a中刪除元素: a.pop()
在這裏插入圖片描述
是不是a指向的對象本身變了? 如果沒看清楚, 我們再像a裏面添加元素, a.insert(3, 8)
在這裏插入圖片描述
所以對於列表而言, 它能添加和刪除元素, 所以是可變的。 但注意是本身可變。 如果我執行這一步操作:a[2].insert(1, 6)
在這裏插入圖片描述
這個如果我們打印a的話, 也會發現a變了, 但是,這個要注意, 這個不能說明a是可變的, 因爲a指向的對象本身並沒有變, 變得只是a[2]指向的對象, 只能說a裏面的元素有可變的, 理解這一點非常重要, 爲啥呢? 我們下面看看元組吧。

我們知道,元組是不可變對象, 但是有時候, 我們也能看似改變它的元素:

a =(1,3,[5,7],9,11,13)

依然是上面的那些元素, 只是把[]換成(), 這時候a就是一個元組了。 我們看看內部結構:
在這裏插入圖片描述
其實, 看元組本身會發現右邊是封閉的了。 這時候我們依然a[2].insert(1, 6), 這個時候我們同樣也會觀察到a裏面的元素有變化。
在這裏插入圖片描述
但是, 我們看a指向的對象本身, 其實沒有變, a內元素也沒有發生增減, 長度還是6, 所以對於不可變對象而言, 一旦創建後,長度就不能發生改變, 但是允許裏面的元素有可變對象。

所以對於list而言, 列表長度有增有減,對象本身會發生改變, 所以它是可變的, 而tuple而言, 允許裏面的元素可變,但是它本身不會發生改變。 所以它是不可變對象。

說了這麼多,不知道聽懂了沒有。

4. list的經典使用案例

下面的這些例子非常經典實用, 不管是刷題還是應用中其實很常見, 順序不分先後。

  1. 判斷list裏面有無重複元素

    def is_duplicated(lst):
        return len(set(lst)) != len(lst)
    
    a = [1, 2, 3, 3]
    is_duplicated(a)
    
  2. 列表的逆序

    def reverse(lst):
    	return lst[::-1]
    
  3. 找出列表中重複的元素
    這個就是count計數, 遍歷一遍,看看有重複的,加入新的數組

    def find_duplicated(l):
        ret = []
        for item in l:
            if l.count(item) > 1 and item not in ret:
                ret.append(item)
        return ret
    
    a = [1, 2, 3, 2, 1]
    b = find_duplicated(a)    # [1, 2]
    
  4. 去掉列表中最大值最小值,然後計算剩餘元素平均值

    def score_mean(lst):
        lst.sort()
        lst2 = lst[1:-1]
        return round((sum(lst2)/len(lst2)), 1)
    
    lst=[9.1, 9.0,8.1, 9.7, 19,8.2, 8.6,9.8]
    score_mean(lst)   # 9.1
    

    這個像不像去掉最高分和最低分取平均, 看一下動畫演示過程:
    在這裏插入圖片描述

  5. 獲得列表的表頭和表尾

    def head(lst):
        return lst[0] if len(lst) > 0 else None    #  這個判斷形式要會
    print(head([]))
    print(head([1, 2, 3]))
    
    def tail(lst):
        return lst[-1] if len(lst) > 0 else None
    
  6. 獲得列表中出現次數最多的元素
    這個還是很實用的, 尋找衆數有沒有? 如果列表裏面只有一個元素出現的次數最多, 那麼可以用max內置函數, 裏面的key設置爲元素的個數比較。

    def mode(lst):
        if not lst:
            return None
        return max(lst, key=lambda v: lst.count(v))   # v 在 lst 的出現次數作爲大小比較的依據
    
    a = [1, 2, 3,4, 2]
    mode(a)    # 2
    

    如果衆數不止一個, 那麼這時候就需要獲得這個衆數之後, 得到它出現的次數, 遍歷列表,把次數相同的返回:

    # 如果是有多個元素的時候
    def mode(lst):
        if not lst:
            return None
        
        max_freq_elem = max(lst, key=lambda v: lst.count(v))
        max_freq = lst.count(max_freq_elem)   # 出現最多的次數
        ret = []
        for i in lst:
            if lst.count(i) == max_freq and i not in ret:
                ret.append(i)
        
        return ret
    
    a = [1, 2, 3, 4, 3, 2]
    mode(a)   # [3, 2]
    
  7. 返回更長的列表
    就是多個列表, 得到長度最長的那個。 在這裏我們看一下max內部到底是咋工作的。

    def max_len(*lists):
        return max(*lists, key=lambda v: len(v))   # v 代表一個 list,其長度作爲大小比較的依據
    
    r = max_len([1, 2, 3], [4,5], [5])
    r    # [1, 2, 3]
    

    這個*可以匹配多個參數, 記不記得函數的可變參數? 在python基礎裏面整理過的。 這裏就是把列表都傳進去, 然後根據max函數,返回最長的列表, 可以看到這個地方的key表示的list的長度。 看一下內部是怎麼實現的:
    在這裏插入圖片描述

  8. 洗牌數據shuffle

    from random import shuffle, randint
    lst = [1, 2, 3]
    shuffle(lst)
    lst
    

    shuffle可以打亂列表, 當然如果是數組的話, 可以用permutation:

    index = np.random.permutation(3)        # 這個是數組的隨機洗牌
    a = np.array(lst)
    a = a[index]
    a
    

    這些在打亂樣本的時候非常實用。

  9. 生成元素對

    a = [i for i in range(10)]
    list(zip(a[:-1], a[1:]))
    
    #
    [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)]
    

    這個在取樣的時候也有時候會用到

  10. 斐波那契數列

    def fibonacci(n):
        if n <= 1:
            return [1]
        fib = [1, 1]
        while len(fib) < n:
            fib.append(fib[len(fib)-1] + fib[len(fib)-2])
        
        return fib
    
    fibonacci(5)
    

    當然,這個方法耗費內存,也不高效, 所以還可以更高效。 用生成器。

    # 上面的方法不高效, 生成器會更簡潔和節省內存
    def fibonacci(n):
        a, b = 1, 1
        for _ in range(n):
            yield a          # 遇到yield之後, 返回,下次再進入函數體, 從yield中的下一句執行
            a, b = b, a+b
    
    list(fibonacci(5))
    

今天的內容就先整理這麼多, 後期會把列表中的一些實用操作都整理到一塊來, 便於學習和查找。

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