循環迭代
無限輪循
我們一樣從一個簡單的情境範例開始,班上一共有五位學生,如下:
students = ['Kobe', 'MJ', 'LBJ', 'Curry', 'KD']
老師每天都有若干的班級事務需要同學們輪流幫忙完成,為了公平起見,採取輪流的制度,也就是當某一位同學協助了某一次的班務,之後要等到其他四位同學也都各幫忙處理過一件班務之後,才又輪到他。
假設今天班務也用一個清單 tasks 表示,老師可以這樣進行班務的分配:
tasks = 'task1 task2 task3 task4 task5 task6 task7'.split() # 假設今天有 7 項事務
for t_idx, task in enumerate(tasks):
s_idx = t_idx % len(students)
student = students[s_idx]
print(student, task)
結果如下:
Kobe task1
MJ task2
LBJ task3
Curry task4
KD task5
Kobe task6
MJ task7
雖然 Kobe 和 MJ 很倒楣的必須負責兩件公務,但總算是依照規則分配好了,但是這樣的寫法顯然不夠漂亮,這個時候 itertools 中的 cycle 可以幫上我們的忙:
from itertools import cycle
students_pool = cycle(students)
for task in tasks:
student = next(students_pool) # 從 pool 中抽取下一名學生
print(student, task)
在這裡,我們利用了 cycle 製作了一個 students_pool,他是一個產生器(迭代器),所以使用 next 方法可以從中拿到下一名學生。cycle 會不斷地輪循 students,所以不論班務有多少都能順利地依照規則選出一名學生來負責。
這樣寫還有另一個好處,students_pool 可以留待隔日繼續使用,下一個抽取出來的學生會是 LBJ 因為循環的狀態被記錄著。於是,此 pool 將可被拿來無限輪循,直到沒有工作為止。
在實務上,我們常會碰到許多需要 分配資源 的代碼。比如說分配伺服器給多個任務以達成分散式運算的目的,我們可以將所有機器的列表 hosts,藉由 cycle 擴充為 hostpool,每個任務都能從 hostpool中抽取出機器,這也保證了一定程度的平均分配。
有限輪循
回到一開始的範例,假設班務的分配僅限於今日(亦或是說任務的數量已知且有限),我們也可以拿 cycle 和 zip 進行搭配,可以寫出更簡潔的代碼:
for student, task in zip(cycle(students), tasks):
print(student, task)
這樣一來,就可以完全 避免在迴圈中取值 了。
同時我們也能發現,因為 zip 的特性,使得 students 的輪循從無限變成了有限(被 tasks 的數量所限制住)是不是很有趣呢?