Python學習之旅—Day04
前言:
前面三篇博客對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