1. 程式人生 > >Python學習之旅—Day04

Python學習之旅—Day04

不同的 iteration 例如 恢復 del 知識 推薦 動態刪除 初學者

前言:

前面三篇博客對Python的基礎知識點進行了相關總結和整理,今天的博客主要專註於解決前面一些知識點的疑難點,並在此基礎上補充一些知識點,以此來加深對相關知識點的理解。


1.列表和字典結合for循環使用時所引發的下標越界和字典大小改變錯誤。

我們往往需要結合for循環來遍歷列表和字典,然後實現相應的邏輯。但由於列表和字典本身是有大小限制的,我們可以通過len(dict)或者len(list)來獲取其長度,如果處理不得當,很容易造成下標越界錯誤。我們來看看一個實際的需求:已知列表li = [11, 22,33,44,66],要求刪除索引值為奇數值的元素,並輸出刪除後的列表。有很多方法可以實現如上需求,我們來看看如下的幾種實現方法:

for i in range(0,len(li)): # 4,0
        del li[i]
print(li)

直接執行如上代碼會直接報錯:IndexError: list assignment index out of range。剛開始也沒註意到這個錯誤,後面經過仔細分析,才發現裏面的道道。來跟隨筆者一起來分析下。

i取值可以為0,1,2,3,4,li = [11, 22,33,44,66]。當i = 0時,刪除li[0]後的列表為:[22,33,44,66],此時li的長度為4,此時i+1,遍歷li中第二個元素:33,刪除元素後的列表變為:

[22,44,66].i繼續+1,遍歷li中第三個元素:66,刪除元素後的列表為:[22,44]。i繼續+1,遍歷li中第4個元素,此時發現列表中僅剩下2個元素。即使再使用li[3],此時就會報列表索引越界的錯誤,我們可以在Pycharm中通過debug來測試我們的想法。

產生上述錯誤的原因在於我們忽略了列表是一個可變對象的容器,在我們對原始列表進行刪除元素時,它的長度是動態改變的。因此很有可能會導致索引越界的錯誤。我們再來看卡如下的例子:

li = [11, 22, 33, 44, 66]   # 0, 1, 2, 3, 4
for num in range(0, len(li)):
    if num % 2 == 1:
        del li[num]
print(li)
#運行結果為:[11,33,44]

上面的代碼可以在Pycharm中運行成功,但是運行結果確是錯誤的,依然是i因為動態刪除列表中的元素會導致列表的長度發生變化,雖然沒有出現索引下標越界異常,但是執行的結果確是錯誤的。

回到上面題目的需求,我們提供了幾種不同的解體思路,最優解當然得用到切片。看看如下幾種解法:

li = [11, 22, 33, 44, 66] # 0,1,2,3,4
new_list = []
for i in range(0, len(li)):
    if i % 2 == 0:
        new_list.append(li[i])
print(new_list)
li = [11, 22, 33, 44, 66] # 0,1,2,3,4
for i in range(len(li)-1, -1, -1):
    if i % 2 == 1:
        del li[i]
print(li)

我們可以看到前面兩種解法都可以達到目的,優先推薦上面的第一種,因為代碼簡潔明了,容易明白。但我們還有一個最簡單的方法,那就是使用切片,一句話搞定:

li = [11, 22, 33, 44, 66] # 0,1,2,3,4
del li[1::2]
print(li)

2.關於元組tuple,這裏有一個重要的知識點補充:當元組中的元素只有一個時,無比在元素後面加上逗號,要不然會造成意想不到的錯誤。我們來用代碼說話:

>>> tu = (1,2,3,4,[11,22,33])
>>> print(tu,type(tu))
(1, 2, 3, 4, [11, 22, 33]) <class tuple>
>>> tu = (1,)
>>> print(tu,type(tu))
(1,) <class tuple>
>>> tu = (1)
>>> print(tu,type(tu))
1 <class int>

從代碼運行的結果可知,如果元組中的元素只有一個,如果不加逗號,那麽它其實相當於一個整數。這點事大家普遍忽略的,無比要記牢。

3.第三點就是關於for循環和字典的遍歷一起使用而引發的。

我們還是來一個實際需求:已知字典dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘},現在要求刪除字典key中包含字符k的鍵值對,然後打印輸出最終的字典。初學者拿到手,肯定是這樣的思路:

dic = {k1: v1, y2: v2, k11: v3}
for item in dic.keys():
    if k in item:
        del dic[item]
print(dic)

但實際上這樣做會報如下的錯誤:RuntimeError: dictionary changed size during iteration。這是因為我們在字典的遍歷過程,刪除了字典中的元素,使得字典的大小發生了改變。

這會在Python中引發RuntimeError。所以要想實現上述的需求,這邊提供兩種解決方法。如下代碼:

dic = {k1: v1, y2: v2, k11: v3, w1: y2}
dic1 = {}
for item in dic.keys():
    if k in item:
        continue
    else:
        dic1[item] = dic[item]
print(dic1)

這種思路采用的是新建一個字典,將不符合條件的篩選出來,然後打印輸出,而不對原始字典做刪減操作,以此避免改變原始字典的長度。我們一起來看看第二種解法:

dic = {k1: v1, y2: v2, k11: v3, w1: y2}
dic_key_list = []  # 用來存儲字典鍵的集合
for key in dic:
    dic_key_list.append(key)

for row in dic_key_list:
    if k in row:
        del dic[row]
print(dic)

這種思路的出發點也是在不改變字典大小的情況下,對字典進行靜態刪除。而不是結合for循環對字典進行動態刪除,這在Python中是不允許的。作為這種解法,還有一種更加Pythonic的方式,如下:

for k in list(dic.keys()):
    if k in k:
        del dic[k]
print(dic)

筆者在學習過程中,曾經碰到如下的解法,通過這種解法引出了for循環和else語句的結合使用,在effective Python這本書中,作者推薦不要這樣使用。但是既然作為初學者,我們有必要了解下,代碼如下:

dic = {k1: v1, y2: v2, k11: v3, w1: y2}
while True:
    for k in dic:
        if k in k:
            del dic[k]
            break
    else:
        break
print(dic)

要想更好的理解這種思路,建議通過debug去觀察,這裏主要明白for......else的用法。當k第一次取k11時,刪除鍵值對‘k11‘: ‘v3‘成功後,會執行break語句,跳出裏面的for循環。按照正常理解,此時應該繼續執行else語句塊。但是這裏要註意,else語句塊要執行,必須等到前面的for循環裏面的其他元素遍歷完畢後才能夠執行。所以通過這種方式,我們可以在遍歷字典的情況下,不動態改變字典的長度來達到刪除元素的目的,也就相當於我刪除完字典中滿足條件的一個鍵值對後,退出for循環,然後再次進入for循環。這就保證了沒有動態修改字典的大小。這裏主要要明白for.....else語句塊的執行流程,如果還不懂,可以在else語句塊後面再加一個break,如下:

dic = {k1: v1, y2: v2, k11: v3, w1: y2}
while True:
    for k in dic:
        if k in k:
            del dic[k]
            break
    else:
        break
    break
print(dic)
#打印結果:{‘y2‘: ‘v2‘, ‘w1‘: ‘y2‘, ‘k11‘: ‘v3‘}

打印結果與實際不符合,這也就印證for...else的執行流程。

4.關於第四點筆者在這裏想探討的是如何往空字典裏面循環加入數據。我們還是來看一個實際需求:有如下值集合 [11,22,33,44,55,66,77,88,99,90,10],將所有大於 66 的值保存至字典的第一個key中,將小於 66 的值保存至第二個key的值中。即: {‘k1‘: 大於66的所有值, ‘k2‘: 小於66的所有值}.要求:只能創建一個空子典dic={}。

這個需求實際考察我們如何動態往字典裏面添加元素。我們先來看看初學者的做法:

li = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
dic = {}  # dic = {‘k1‘:[], ‘k2‘:[]}
for i in li:
    if i > 66:
        dic[k1] = [i, ]
    elif i < 66:
        dic[k2] = [i, ]
    else:
        continue
print(dic)
#打印結果為:{‘k2‘: [55], ‘k1‘: [90]}

很明顯上面的結果沒有滿足我們的需求,通過分析可知,第一步dic[‘k1‘] = [i, ]雖然能夠往空子典中加入一個元素,該元素的key為k1,value為一個列表。比如{‘k1‘: [90],}但是第二次如果遍歷的元素是88,那麽執行dic[‘k1‘] = [i, ]後,我們發現變成了這樣:{‘k1‘: [88],}.我們知道,當字典中存在某個key時,如果第二次再往該字典中添加相同key的鍵值對,那麽字典會認為是對這個字典中的key所對應的value值進行了更新。也就是由90變成了88.所以直到我們遍歷完所有的元素,最後,列表中只有一個元素。

通過上面的分析可知,關鍵問題在於解決如何保證key相同,二次也能加進去。最後的思路為:如果key第一次存在,那麽直接dic[‘k1‘] = [i, ],否則我們直接在列表後面添加元素即可。代碼如下:

li = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
dic = {}  # dic = {‘k1‘:[], ‘k2‘:[]}
for i in li:
    if i > 66:
        if k1 not in dic:
            dic[k1] = [i, ]
        else:
            dic[k1].append(i)
    elif i < 66:
        if k2 not in dic:
            dic[k2] = [i, ]
        else:
            dic[k2].append(i)
    else:
        continue
print(dic)

5.關於第五點,這裏有一個要註意的地方,就是查看字典中value是否在其中。這涉及到循環遍歷字典中的value,同時要主要區別in和==的區別。看如下的代碼:

循環實現,檢查“v1”是否在字典 dic = {‘k1‘: ‘v1‘,‘k2‘: ‘v2‘} 的值中。初學者的做法往往是這樣的:

dic = {k1: v1, k2: v2}
for item in dic.values():
    if v1 == item:
        print(True)
        break
    else:
        print(False)
#通過執行我們發現結果有時為:True,而有時同時打印False和True.

思考:為什麽會遇到這種情況?!究其原因我們發現,由於字典是無序的,所以如果第一次比較的value值是v2,那麽它會打印完畢Fasle後,再去拿v1做比較,然後打印True.

如果我們運氣好,那麽第一次比較的是v1,所以會直接打印False.我們即使將for循環變為 for item in list(dic.values()),即將dic.values封裝為一個list,結果也是和上面一樣。因為從字典中取value時就具有隨機性,不知道先取到哪個。所以list(dic.values())的值有兩種情況:[‘v1‘, ‘v2‘]和[‘v2‘, ‘v1‘]。那具體怎麽解決呢?主要有兩種方法可以解決,一是通過for.....else語句改寫代碼,如下:

dic = {k1: v3, k2: v2}
for item in dic.values():
    if v1 == item:
        print(True)
        break
else:
    print(False)

第二種解法是設置一個標識符,代碼如下:

result = False
dic = {k1: v1, k2: v2}
for item in dic.values():
    if v1 == item:
        result = True
        break
print(result)

另外關於本題,要註意的是in和==的區別,例如,如果value中有的值為k111,那麽使用in條件判斷時,‘k1‘ in k111,則成立。如果我們想判斷的是值是否相等,要使用==。


以上就是第4次博客的主要內容,先暫時講解這麽多!後期會陸續更新和增加!

   

---恢復內容結束---

Python學習之旅—Day04