while 迴圈、for 迭代

April 14, 2022

Python提供 while 迴圈,可根據指定條件式來判斷是否執行迴圈本體。

while 迴圈

先來看個很無聊的遊戲,看誰可以最久不碰到 5 這個數字:

from random import randint

while (number := randint(0, 9)) != 5:
    print(number)

print('我碰到 5 了....Orz')

random 模組的 randint 會隨機產生 0 到 9 的整數,這邊運用了 Python 3.8 新增的海象運算子指定給 number,加上括號表示指定運算式會先運算,之後再判斷 number 是否為 5,若判斷為 True 就執行迴圈。一個參考的執行結果如下:

1
1
9
8
7
我碰到 5 了....Orz

運用迴圈的場合,可能是海象運算子的使用,就這個例子來說,若不使用海象運算子,要有相同執行結果,可以如下撰寫:

from random import randint

number = 0
while number != 5:
    number = randint(0, 9)
    if number == 5:
        break

    print(number)
    
print('我碰到5了....Orz')

在迴圈中若執行 break,就會中斷 while 迴圈,因此在 number 為 5 的情況下,print(number) 不會被執行;相對之下,使用海象運算子的版本比較簡潔。

while 迴圈中,還可以使用 continue,遇到 continue 的話,該次不執行後續的程式碼,直接進行下次迴圈。

for in 迭代

如果想循序迭代某個序列,例如字串、listtuple,可以使用 for in 陳述句。例如,迭代使用者提供的命令列引數,轉為大寫後輸出。

import sys

for arg in sys.argv:
    print(arg.upper())

要被迭代的序列,是放在 in 之後,對於字串、listtuple 等具索引特性的序列,for in 會依索引順序逐一取出元素,並指定給 in 之前的變數。一個執行結果如下:

>python uppers.py justin monica irene
UPPERS.PY
JUSTIN
MONICA
IRENE

如果在迭代的同時,需要同時提供索引資訊,那麼有幾個方式,例如使用 range 產生一個指定的數字範圍,使用 for in 進行迭代,再利用迭代出來的數字作為索引。例如:

>>> for i in range(len(name := 'Justin')):
...     print(i, name[i])
...
0 J
1 u
2 s
3 t
4 i
5 n
>>>

range 的形式是 range(start, stop[, step])start 省略時,預設是 0,step 是步進值,省略時預設是 1,因此上例中, range(len(name := 'Justin')) 的結果,會產生 0 到 5 的數字。

你也可以使用 zip,將兩個序列的各元素,像拉鏈般一對一配對(這就是為什麼它叫zip的原因,實際上 zip 可以接受多個序列),產生一個新的 list,當中每個元素都是個 tuple,包括了配對後的元素。

>>> list(zip([1, 2, 3], ['one', 'two', 'three']))
[(1, 'one'), (2, 'two'), (3, 'three')]
>>>

zip 會傳回一個 zip 物件,這個物件實際上還不包括真正配對後的元素,也就是具有惰性求值的特性(range 產生的 range 物件也是)。zip 物件可以使用 for in 迭代,因此若迭代時需要索引資訊,可以如下:

name = 'Justin'
for i, c in zip(range(len(name)), name):
    print(i, c)

在這邊還使用了 tuple 拆解的特性,將每一對 tuple 的元素,拆解指定給 ic 變數。

實際上,若真的要迭代時具有索引資訊,建議使用 enumerate 而不是 rangeenumerate 會傳回 enumerate 物件,一樣具有惰性求值特性,且可使用 for in 迭代,enumerate 可取得 tuple 元素,例如:

>>> list(enumerate('Justin'))
[(0, 'J'), (1, 'u'), (2, 's'), (3, 't'), (4, 'i'), (5, 'n')]
>>>

因此,迭代時具有索引資訊,也可以使用以下方式:

for i, c in enumerate('Justin'):
    print(i, c)

預設的情況下,enumerate 會從 0 開始計數,如果想從其他數字開始,可以在 enumerate 的第二個引數指定。例如從 1 開始:

for i, c in enumerate('Justin', 1):
    print(i, c)

之後會看到,只要是實作了 __iter__ 方法的物件,都可以透過 __iter__ 方法傳回一個迭代器(Iterator),這個迭代器可以使用 for in 迭代,像是之前的 rangezipenumerate 物件就是如此。

set 也實作了 __iter__ 方法,因此可以進行迭代,不過因為 set 是無序的,只能迭代出元素,但不一定是你想要的順序。

至於想要迭代 dict 鍵值的話,可以使用它的 keysvaluesitems 方法,它們各會傳回 dict_keysdict_valuesdict_items 物件,都實作了 __iter__ 方法,因此也可以使用 for in 迭代。舉例來說,來同時迭代 dict 的鍵值:

>>> passwds = {'Justin' : 123456, 'Monica' : 54321}
>>> for name, passwd in passwds.items():
...     print(name, passwd)
...
Justin 123456
Monica 54321
>>>

因為 dict_items 的元素是 tuple,各包括了一對鍵、值,同樣地,這邊使用了 tuple 拆解的特性,將 tuple 的鍵、值拆解給 namepasswd 變數。如果直接針對 dict 進行 for in 迭代,預設會進行鍵的迭代。

for 也可以使用 break 來中斷迭代,在 for in 迭代遇到 continue 的話,該次不執行後續的程式碼,直接進行下次迭代。

以下是利用 continue 的特性,實作出一個只顯示小寫字母的程式:

for letter in input('輸入一個字串:'):
    if letter.isupper():
        continue

    print(letter, end='')

這個範例在遇到大寫字母時,就會執行 continue,因此該次不會執行 print。一個執行範例如下:

輸入一個字串:This is a Question!
his is a uestion!

whilefor in 都有可與 else 配對的形式,然而不建議使用,因為寫出來的程式碼可讀性不佳,這邊就不討論了。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter