Python迴圈結構用法

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/12/17/10129317.html
-Advertisement-
Play Games

本文介紹python中的while迴圈、for迴圈。在python中for可以用於迴圈,也可用於另一種近親的列表解析,列表解析是python中非常重要的特性,詳細內容見後面的文章。 一般來說,python寫for迴圈比寫while更容易、方便,而且python中的for比while效率要更高,如果可 ...


本文介紹python中的while迴圈、for迴圈。在python中for可以用於迴圈,也可用於另一種近親的列表解析,列表解析是python中非常重要的特性,詳細內容見後面的文章。

一般來說,python寫for迴圈比寫while更容易、方便,而且python中的for比while效率要更高,如果可以,用for而不是while。

while迴圈

python中的while/for迴圈和其它語言的while迴圈有些不一樣,它支持else分支。結構如下:

while <CONDITION>:
    CODE
else:
    CODE_ELSE

註意,condition部分只能是表達式,不能是語句,所以condition中不能包含賦值語句,如while a = x:是錯誤的。

while和for的else分支表示當正常退出while/for迴圈的時候所執行的代碼分支。所謂正常退出,是指不是通過break跳出的情況,也就是正常把所有迴圈條件輪完的情況。這對於那些需要通過設置標誌位來判斷的情況來說非常方便,而標誌位通常是用於離開迴圈的時候,提供一個額外的標記、通知功能,比如退出迴圈時想找的數據是否找到。

例如搜索一個列表,併在退出時告知是否找到。如果使用標誌位來實現,如下:

found = False

while x and not found:
    if match(x[0]):
        print("found it")
        found = True
    else:
        x = x[1:]

if not found:
    print("not found")

如果通過else,則邏輯更清晰:

while x:
    if match(x[0]):
        print("found it")
        break
    x = x[1:]
else:
    print("not found")

再例如,判斷一個數(如下麵的y)是否是質數。

y = 21

x = y // 2
while x > 1:
    if y % x == 0:
        print( y, "has a factor: ", x)
        break
    x -= 1
else:
    print("y is a prime")

想象一下如果不使用while的else,上面的功能該如何實現。

pass、break、continue、else

這幾個關鍵字都能用在while/for中。

  • break:退出整個迴圈(while/for),如果嵌套了迴圈,則退出break所在的那個層次
  • continue:直接跳到下一次迴圈
  • else:在迴圈正常退出(不是break中斷的迴圈)時執行的所執行的預設代碼塊
  • pass:在python中作為空的占位符,表示什麼也不做。比如:
    • if x:pass
    • while x:pass
    • def x():pass
    • class x:pass

在python 3.x中,pass的另一種方式是...,它也表示什麼也不做的占位符。

for迴圈

python中的for是一個通用的序列迭代器,和bash的for語法類似。python中沒有for(i=0;i<N;i++)的語法,但for結合range可以實現一樣的功能,後文介紹。

for語法:

for i in <Sequence>:
    CODE
else:
    CODE_ELSE

每次迭代時,for從序列中取一個元素賦值給控制變數i,下一輪迭代取下一個元素再賦值給i。和其它語言不太一樣,for中的控制變數不會在for迴圈完後消失,它會保持最後一個被迭代的元素值。之所以會這樣,是因為其它語言中for是一個代碼塊,而python中for不算是代碼塊,也就是說沒有自己的名稱空間。

實際上不止序列,只要是可迭代的對象,都能用for進行遍歷。關於什麼是可迭代的,將專門在迭代器相關的文章中解釋。

例如,遍歷一個字元串,因為它是序列。

for i in 'xiaofang':
    print(i)

print("var i after: ",i)   # 輸出g

遍歷一個列表:

L = ["aa","bb","cc"]
for i in L:
    print(i)

嵌套:

L = ["aa","bb","cc"]
for i in L:
    for j in i:
        print(j)

計算序列中所有數值的和:

L = [1,2,3,4,5]
sum = 0
for i in L:
    sum += i

print(sum)

for迭代字典

for迭代字典時,迭代的是key

D = {'a': 1,
     'b': 2,
     'c': 3}

for key in D:
    print(key, "=>", D[key])

其它迭代字典的幾種方式:

1.通過keys()迭代字典

for k in D.keys():
    print(key, "=>", D[key])

2.直接迭代字典的value

for v in D.values():
    print(v)

3.同時迭代key和value

for k, v in D.items():
    print(k, v)

for中的賦值和序列解包

for迭代時,實際上是從可迭代對象中取元素併進行賦值的過程,python中各種變數賦值的方式在for中都支持。而且,python中變數賦值是按引用賦值的,所以每次迭代過程中賦值給控制變數的是那個元素的引用,而不是拷貝這個元素並賦值給控制變數。所以,如果賦值給控制變數的是可變對象時,修改控制變數會直接修改原始數據。

例如:

T = [(1, 2), (3, 4), (5, 6)]
for i in T:
     print(i)

for (a, b) in T:
    print(a, b)

輸出:

(1, 2)
(3, 4)
(5, 6)
1 2
3 4
5 6

for還支持序列解包的賦值形式。

例如:

for (a, *b, c) in [(1, 2, 3, 4), (5, 6, 7, 8)]:
    print(a, b, c)

結果:

1 [2, 3] 4
5 [6, 7] 8

因為python是按引用賦值的,所以控制變數都是直接指向迭代元素的,而不是拷貝副本後進行賦值。看下麵的結果:

L = [1111, 2222]
print(id(L[0]))
print(id(L[1]))

print("-" * 15)

for i in L:
    print(id(i))

輸出結果:

46990096
46990128
---------------
46990096
46990128

可見,變數i和列表中元素的記憶體地址是一致的。

正因為是按引用賦值,所以迭代過程中修改賦值給控制變數i的不可變對象時會創建新對象,從而不會影響原始數據,但如果賦值給i的是可變對象,則修改i會影響原始數據。

例如:

L = [1111, 2222]

for i in L:
    i += 1

print(L)

列表L不會改變:

[1111, 2222]

而下麵修改控制變數i會改變原始對象:

L = [[1],[1,2],[1,2,3],[1,2,3,4]]

for i in L:
    i.append(0)

print(L)

結果:

[[1, 0], [1, 2, 0], [1, 2, 3, 0], [1, 2, 3, 4, 0]]

for + range

python中並沒有直接支持for i=0;i<N;i++的for語法,但是,通過for + range(),可以實現類似的功能。

先介紹一下range()。它像Linux下的seq命令功能一樣,用來返回一些序列數值。range()返回一個可迭代對象,目前無需知道可迭代對象是什麼,只需知道它可以轉換成list、tuple、Set,然後可以在通用迭代器for中進行迭代。

>>> range(3)
range(0, 3)

>>> list(range(3)),set(range(3)),tuple(range(3))
([0, 1, 2], {0, 1, 2}, (0, 1, 2))

可見,range()返回的序列值是前閉後開的。

還可以指定起始值,步進(每隔幾個數)。

>>> list(range(1,5))
[1, 2, 3, 4]

>>> list(range(-1,5))
[-1, 0, 1, 2, 3, 4]

>>> list(range(-1,5,2))
[-1, 1, 3]

步進值指定為負數的時候,可以生成降序的序列值。

>>> list(range(10,5,-1))
[10, 9, 8, 7, 6]

range()返回了生成序列值的迭代器後,可以用for來進行迭代。

for i in range(3):
    print(i)

range()還經常用於for中作為序列的索引位。例如:

L = ["a","b","c","d"]
for i in range(3):
    print(L[i])

分析for + range迭代的過程

下麵兩個例子,在結果上是等價的:

for i in range(3):
    print(i)

for i in [0,1,2]:
    print(i)

但除了結果上,過程並不一樣。range()既然返回可迭代對象,說明序列數值是需要迭代一個臨時生成一個的,也就是說range()從始至終在記憶體中都只占用一個數值的記憶體空間。而[0,1,2]則是在記憶體中占用一個包含3數值元素的列表,然後for從這個列表對象中按照索引進行迭代。

再通俗地解釋下,for i in range(3)開始迭代的時候,生成一個數值0,第二次迭代再生成數值1,第三次迭代再生成數值2,在第一次迭代的時候,1和2都是不存在的。而[0,1,2]則是早就存在於記憶體中,for通過list類型編寫好的迭代器進行迭代,每次迭代從已存在的數值中取一個元素。

所以,在效率上,使用range()要比直接解析列表要慢一點,但是在記憶體應用上,range()的方式要比直接解析已存在的列表要好,特別是列表較大的時候。一般來說,python中最簡單的方式總是最好的、效率很大可能上也是最高的,所以能直接解析的時候,不使用range的效率總會更高一些。

這種效率的區別,也可以應用於其它迭代方式的分析上。例如,按行讀取文件的兩種方式:

for i in open("filename"):
    print(i)

for i in open("filename").readlines():
    print(i)

第一種方式,open()返回一個文件迭代器,每次需要迭代的時候才會去讀需要的那一行,也就是說從始至終在記憶體中都只占用一行數據的空間。而第二種通過readlines()讀取時,它會一次性將文件中所有行都讀取到一個列表中,然後for去迭代這個列表。如果文件比較大,第二種方式可能會占用比較大的記憶體,甚至可能比原文件大小還要大,因為很可能會一次性為400M的文件分配500M記憶體,以免後續不斷的記憶體分配。

for + range的步進以及分片

無論是range(),還是序列的分片計數,都支持步進。例如步進為2:

>>> list(range(1,6,2))
[1, 3, 5]

>>> L = [1,2,3,4,5]
>>> L[::2]
[1, 3, 5]

它們都能用於for。

for i in range(1,6,2):
    print(i)

L = [1,2,3,4,5]
for i in L[::2]:
    print(i)

它們的結果是一樣的。但是和前面分析的一樣,range除了在記憶體應用上比較有優勢,在效率上是不及直接列表解析的,包括這裡分片步進。

for修改列表元素

有一個列表,想要為列表中的值都加1。

L = [1,2,3,4]
for i in L:
    i += 1

這是無效的,雖然python中是按照引用進行賦值的,但數值類型是不可變類型,所以每次修改i實際上都會創建新的數據對象,並不會直接影響L中的元素。這些前文已經解釋過了。

如果想要修改L本身,直接迭代L是沒法實現的,可以通過迭代它的索引,然後通過索引的方式來修改L的元素值。例如:

L = [1,2,3,4]
for i in range(len(L)):
    L[i] += 1
print(L)       # 輸出:[2,3,4,5]

通過while也可以實現。但更簡單的方式是後面的文章要詳細解釋的"列表解析":

L = [1,2,3,4]

L = [x + 1 for x in L]

print(L)

for + zip並行迭代

zip()函數可以將多個序列(實際上是更通用的可迭代對象)中的值一一對應地取出來,然後放進一個元組中。它也返回一個可迭代對象,可以直接通過list/set等函數將它們的內容一次性展現出來。

例如:

L = [1,2,3,4]
S = {'a','b','c','d'}

>>> zip(S,L)
<zip object at 0x03684148>
>>> list(zip(S,L))
[('d', 1), ('a', 2), ('b', 3), ('c', 4)]

註意,集合是無序的,所以這裡從S中去的元素是隨機順序的。但無論如何,已經可以看出zip()的功能了:從容器1和容器2(可是更多個容器)中同時取出一個元素,組成元組返回,再取第二個元素返回。

>>> list(zip(L,L))
[(1, 1), (2, 2), (3, 3), (4, 4)]

如果容器中元素數量不等,則以長度最短的為基準進行截斷。例如:

L1 = [1,2,3,4,5]
L2 = [11,22,33,44,55,66]
L3 = [111,222,333]

>>> list(zip(L1,L2,L3))
[(1, 11, 111), (2, 22, 222), (3, 33, 333)]

zip()還常用於構造dict,例如:

keys = ['a', 'b', 'c', 'd']
values = [1, 3, 5, 7]
D = dict(zip(keys, values))

>>> D
{'a': 1, 'b': 3, 'c': 5, 'd': 7}

瞭解了zip(),就可以將它結合for來進行並行迭代:從每個zip()返回的元組中取來自各個容器中的元素。

例如:

L1 = [1,2,3,4,5]
L2 = [11,22,33,44,55,66]
L3 = [111,222,333]

for (x, y, z) in zip(L1,L2,L3):
    print("%d + %d + %d = %d" % (x, y, z, x + y + z))

結果:

1 + 11 + 111 = 123
2 + 22 + 222 = 246
3 + 33 + 333 = 369

enumerate()取得索引位和元素

在其他語言中,可能會有專門的工具在迭代每一個序列元素時同時取得這個元素的索引位和元素值。python中可以通過enumerate()來實現。

例如:

>>> L =  ['a', 'b', 'c', 'd']

>>> list(enumerate(L))
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

於是,可以通過for迭代器來迭代enumerate()生成的(index, value)元素:

for (k, v) in enumerate(L):
    print(k,v)

enumerate()還可以用它的第二個參數指定從哪個索引值開始標記索引。例如:

>>> list(enumerate(L, 2))
[(2, 'a'), (3, 'b'), (4, 'c'), (5, 'd')]

需要註意的是,像dict這樣的類型不應該去用enumerate()去取索引和值,因為它會將dict的key作為元素值,並自己生成數值索引,也就是說dict的value被丟棄了。

>>> D
{'a': 1, 'b': 3, 'c': 5, 'd': 7}

>>> list(enumerate(D))
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

for迭代的陷阱

for是一個通用的迭代器,它按照next的方式一次取一個元素,下一輪迭代取下一個元素。所以,如果在for內部修改了正在迭代的序列(所以這裡是說可變序列,且特指列表類型),可能會引起一些奇怪現象。

這是for的一個陷阱,或者說是迭代器的一個陷阱:迭代的對象在迭代過程中被修改了。

陷阱一

迭代操作是遞歸到數據對象中去的,而不是根據變數名進行迭代的。也就是說迭代的對象是記憶體中的數據對象。

例如:

L = [1,2,3,4]
for i in L:
    ...

這個for迭代器在迭代剛開始的時候,先找到L所指向的迭代對象,即記憶體中的[1,2,3,4]。如果迭代過程中如果L變成了一個集合,或另一個列表對象,for的迭代並不會收到影響。但如果是在原處修改這個列表,那麼迭代將會收到影響,例如新增元素也會被迭代到。

看下麵的例子:

L = ['a','b','c','d','e']

## 原處修改列表,新元素f、g也會被迭代
for i in L:
    if i in "de":
        L += ["f", "g"]
    print(i)

## 創建新列表,新元素f、g不會被迭代
for i in L:
    if i in "de":
        L = L + ["f", "g"]
    print(i)

陷阱二

例如,迭代一個列表,迭代過程中刪除一個列表元素。

L = ['a','b','c','d','e']
for i in L:
    if i in "bc":
        L.remove(i)
        print(i)

print(L)

輸出的結果將是:

b
['a', 'c', 'd', 'e']

這個for迴圈的本意是想刪除b、c元素,但結果卻只刪除了b。通過結果可以發現,c根本就沒有被for迭代。之所以會這樣,是因為迭代到b的時候,滿足if條件,然後刪除了列表中的b元素。正因為刪除操作,使得列表中b後面的元素整體前移一個位置,也就是c元素的索引位置變成了index=1,而index=1的元素已經被for迭代過(即元素b),使得c幸運地逃過了for的迭代。

如果迭代並修改的是集合或字典呢?將會報錯。雖然它們是可變序列,但是它們是以hash key作為迭代依據的,只要增、刪元素,就會導致整個對象的順序hash key發生改變,這顯然是編寫這兩種類型的迭代器時所需要避免的問題。如下:

D = {'a':1,
     'b':2,
     'c':3,
     'd':4,
     'e':5}

for i in D:
    if i in "bc":
        L.remove(i)
        print(i)

print(L)

報錯:

b
Traceback (most recent call last):
  File "g:/pycode/lists.py", line 12, in <module>
    for i in D:
RuntimeError: dictionary changed size during iteration
S = {'a','b','c','d','e'}

for i in S:
    if i in "bc":
        S.remove(i)
        print(i)

print(S)

報錯:

b
Traceback (most recent call last):
  File "g:/pycode/lists.py", line 4, in <module>
    for i in L:
RuntimeError: Set changed size during iteration

迭代並修改集合、字典是非常常見的需求,但很多第三方模塊在迭代並修改它們的時候都隱隱忽略了這種問題。那麼如何實現這種需求且不會出錯?可以考慮迭代它們的副本,並修改它們自身

例如:

D = {'a':1,'b':2,'c':3,'d':4,'e':5}

for i in D.copy():
    if i in "bc":
        D.pop(i)
        print(i)
print(D)


S = {'a','b','c','d','e'}

for i in S.copy():
    if i in "bc":
        S.remove(i)
        print(i)
print(S)

結果:

b
c
{'a': 1, 'd': 4, 'e': 5}
c
b
{'e', 'd', 'a'}

註意,別使用dict的keys()函數,在python 2.x是可以的,因為返回的是一個列表,但是在python 3.x中,它返回的是一個迭代器。

除了使用copy(),使用其它的方式也可以,只要保證迭代的對象和修改的對象不是同一個對象即可。例如,list()方法轉換Set/Dict,在轉換的過程中會創建新的數據對象,所以迭代和修改操作是互不影響的。

D = {'a':1,'b':2,'c':3,'d':4,'e':5}

for i in list(D):
    if i in "bc":
        D.pop(i)
        print(i)

print(D)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] 本篇是內部培訓交流會的摘要總結。 培訓PPT 和 示例代碼 已托管至我的github倉庫: "https://github.com/dashnowords/blogs/tree/master/Demo/rebuild angularjs controller" 一. 結構拆分 1. 小型 ...
  • pygame製作"停不下來的奧爾加團長"小游戲 一、pygame簡介 Pygame 是一組用來開發游戲軟體的 Python 程式模塊,基於 SDL 庫的基礎上開發。允許你在 Python 程式中創建功能豐富的游戲和多媒體程式,Pygame 是一個高可移植性的模塊可以支持多個操作系統。用它來開發小游戲 ...
  • 工廠模式 一:簡單工廠模式 1. 問題的引出 我們打算做一個製作pizza的系統,從訂購到出貨,初始代碼如下: 客戶端通過調用pizza類的orderPizza方法來創建pizza,根據type的不同來獲取不同種類的pizza,然而以上的設計存在著很多問題: 1. Pizza類中存在大量的if el ...
  • 《設計模式:可復用面向對象軟體基礎》 這本書還沒看完,但是絕對是案頭必備,雖然用C++寫的代碼,並且是四個牛人寫的風格(相關知識背景與程式員不同),但是一旦開始理解設計模式以後,再回過頭會發現這本書的定義和描述最容易理解和記憶。 《Head First設計模式》 自學、初學的推薦用書。我也是通過這本 ...
  • 原文鏈接:SAP ABAP7.50系列之預定義數據結構 公眾號:SAP Technical 前言部分 先說一下,之前有些文章被轉載之後也沒有註明,這個就比較不好。如果你覺得本文寫的並不好,那麼可以直接去看HELP,這樣更直接,我這裡只是做記錄,如果讀者朋友感興趣,可以關註公眾號,也可以在本文末留言, ...
  • 2.帶getter和setter屬性 3.對象私有欄位 在Scala中,方法可以訪問該類的所有對象的私有欄位 4.Bean屬性 當你將Scala欄位標註為@BeanProperty時,會自動生成四個方法 5.輔助構造器 6.主構造器 7.嵌套類 ...
  • 你們可能會想,棧長這麼菜的嗎?5分鐘都堅持不了? 本文說起來會有點尷尬,畢竟這是棧長我曾經經歷過的故事。。。 那時候的棧長還真菜,每天寫著 if/ for 及一些簡單的業務邏輯代碼,雖工作有些日子了,但技術水平還停留在剛畢業的起步階段。。。 記得,那是一個周末,棧長去某知名互聯網公司面試,好像不到五 ...
  • 超時 網路請求不可避免會遇上請求超時的情況,在 requests 中,如果不設置你的程式可能會永遠失去響應。超時又可分為連接超時和讀取超時。 連接超時 連接超時指的是在你的客戶端實現到遠端機器埠的連接時(對應的是connect()),Request 等待的秒數。 import timeimport ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...