for 迴圈的一些細節
天天在用的 for 迴圈,還老是遇上坑。所以晚上有時間,寫了一篇對 for 迴圈的解釋,做個記錄。同時對迴圈有一個更深入的理解吧。
下面是一個 Python 中 for 迴圈最基本的使用:
nums = [1,2,3,4,5,6]
for num in nums:
print(num)
來對比一下 Java 中的 for 迴圈,先上程式碼
int[] nums = [1,2,3,4,5,6,7];
for(int i =0;i<nums.length();i++){
System.out.print(i);
}
這兩種方式除了語法之外還有什麼區別呢?先來研究下 Python 中的 for 迴圈:
Python 中的 for 語句與你在 C 或 Pascal 中所用到的有所不同。 Python 中的 for 語句並不總是對算術遞增的數值進行迭代(如同 Pascal),或是給予使用者定義迭代步驟和暫停條件的能力(如同 C),而是對任意序列進行迭代(例如列表或字串),條目的迭代順序與它們在序列中出現的順序一致。 例如(此處英文為雙關語):
這是 Python 3.8 文件中對於 Python for 語句的解釋。這裡來看一下 對任意序列進行迭代(例如列表或字串)這句話:
先來解釋一下幾個名詞。任意序列。
所謂序列,指的是一塊可存放多個值的連續記憶體空間,這些值都按一定順序排列,可透過每個值所在的編號(稱為索引)來訪問他們
學過資料結構課的同學可以知道,陣列就是一種序列,他在記憶體中就是一塊連續的記憶體空間,具有快速隨機訪問的特性。對於 Python 中來說,這個任意序列則可以指list
,dict
,tuple
等,因為這些型別,Python 在底層都對他們做了不同的實現,比如說list
本質上是一個長度可變的連續陣列,有需要的同學可以更深入的去了解一下具體的實現。
再來說一下迭代的概念:
迭代 一般指更新換代,迭代操作對應 Python 中的 for 迴圈等內建工具。這裡有兩個重要的概念,可迭代物件
和迭代器
,放一下 Python 官方文件中對於迭代器的描述:
Python 支援在容器中進行迭代的概念。 這是透過使用兩個單獨方法來實現的;它們被用於允許使用者自定義類對迭代的支援。 將在下文中詳細描述的序列總是支援迭代方法。
容器物件要提供迭代支援,必須定義一個方法:
container.**iter**()
返回一個迭代器物件。 該物件需要支援下文所述的迭代器協議。 如果容器支援不同的迭代型別,則可以提供額外的方法來專門地請求不同迭代型別的迭代器。(支援多種迭代形式的物件的例子有同時支援廣度優先和深度優先遍歷的樹結構。)此方法對應於 Python/C API 中 Python 物件型別結構體的 tp_iter 槽位。
迭代器物件自身需要支援以下兩個方法,它們共同組成了 迭代器協議:
iterator.**iter**()
返回迭代器物件本身。 這是同時允許容器和迭代器配合 for 和 in 語句使用所必須的。 此方法對應於 Python/C API 中 Python 物件型別結構體的 tp_iter 槽位。
iterator.**next**()
從容器中返回下一項。 如果已經沒有項可返回,則會引發 StopIteration 異常。 此方法對應於 Python/C API 中 Python 物件型別結構體的 tp_iternext 槽位。
Python 定義了幾種迭代器物件以支援對一般和特定序列型別、字典和其他更特別的形式進行迭代。 除了迭代器協議的實現,特定型別的其他性質對迭代操作來說都不重要。
一旦迭代器的 next() 方法引發了 StopIteration,它必須一直對後續呼叫引發同樣的異常。 不遵循此行為特性的實現將無法正常使用。
翻譯一下,首先這個容器也就是前面所說的任意序列,需要實現一個iter()
的方法,這樣子可以返回一個迭代器物件,然後這個物件呢自己要遵守這個協議,也就是迭代器協議,滿足了以上條件,那麼你就是一個合格的迭代器了,可以被 for 迴圈進行迭代了。
迭代器的協議呢就是你要實現這兩個方法,一個是返回物件本身的iter()
,一個呢是返回下一項的next()
,同時呢如果沒有下一項,你要丟擲一個異常情況,即StopIteration
異常。字面理解就是停止迭代的意思。
思考一下,為什麼需要實現這兩個方法呢?
首先iter()
方法返回是物件本身,透過呼叫next()
的方法不斷迭代更新。這個就是 for 迴圈的本質。
那麼為什麼不一次性的載入到記憶體呢?這就是迭代器的優勢,它不像列表定義的時候那樣,如果有一千萬個數字構成的列表,那麼列表會一次性的載入到記憶體中,這會佔用超過 400M 的記憶體,而使用迭代器的話,它只會佔用幾十個位元組的空間,因為他沒有載入所有元素到記憶體,而是隻有在迭代過程中不斷呼叫next()
方法才返回該元素。(這與懶載入的方式有點相似)
再來看看 Java 中的 for 迴圈,Java 中目前有三種形式的 for 迴圈實現:
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ",");
}
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
for (Integer i : list) {
System.out.print(i + ",");
}
第一種是最普通的 for 迴圈,透過迴圈數字的方式來控制流程,就不細說了。
第二種則是類似於 Python 中的迭代器,也是呼叫了next()
方法來返回下一項。
第三種一般稱為增強 for,也就是 foreach,是 Java 提供的一種語法糖,反編譯後也可以看到他的底層實現也是迭代器模式,但是不需要自己生成一個迭代器物件。
對比一下,Python 中和 Java 中的 for 迴圈,在迭代器方面的使用基本是一致的,都遵守了迭代器的協議。
在使用 for 迴圈過程中,還有一個很容易遇到的坑:
Python 官方文件中有一段話:
在遍歷同一個集合時修改該集合的程式碼可能很難獲得正確的結果。通常,更直接的做法是迴圈遍歷該集合的副本或建立新集合:
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
這是因為在你迴圈遍歷時,當原來的物件數量發生變化時,比如進行了刪除的操作,那麼這個迭代器的索引內容不會同步改變,在 Python 中會發現輸出內容中會有重複項,在 Java 中則會丟擲異常。
針對於這個問題,有多種處理方法,一種就是官方所說,建立副本或新集合來進行刪除操作,這樣就不會引起迭代器中的內容了。還有一種就是在 Java 中可以呼叫迭代器本身的remove()
方法,而不是 list 的remove()
方法,這樣就會維護索引的一致了。
相關文章
- 迴圈佇列的實現及細節佇列
- Termux使用的一些細節UX
- 解析SwiftUI佈局細節(二)迴圈輪播+複雜佈局SwiftUI
- 第八小節 for 迴圈
- 初學Golang的一些細節Golang
- Git 的一些使用細枝末節Git
- 一些 html+css 細節HTMLCSS
- 初學C++的一些小細節(1)C++
- Will it finally: 關於 try/catch 的一些細節
- 第十九節:Java基本資料型別,迴圈結構與分支迴圈Java資料型別
- 關於最小迴圈節的幾種求法[原創]
- 記錄一些React的一些細節,會不斷更新React
- for 迴圈與 while 迴圈While
- while迴圈 case迴圈While
- C語言——迴圈結構(for迴圈,while迴圈,do-while迴圈)C語言While
- 從一個案例,細說瀏覽器的事件迴圈瀏覽器事件
- 無限for迴圈(死迴圈)
- 迴圈中的非同步&&迴圈中的閉包非同步
- for迴圈的概念
- for迴圈的理解
- C#程式設計基礎第七課:C#中的基本迴圈語句:while迴圈、do-while迴圈、for迴圈、foreach迴圈的使用C#程式設計While
- while迴圈以及do while迴圈While
- 探討兩種迴圈表示方法的區別,while迴圈與for迴圈的小總結While
- if迴圈
- 迴圈
- for迴圈
- if for迴圈
- For 迴圈
- 落幕再談,「WAIC 2024」的一些細節與預判AI
- 執行緒池中你不容錯過的一些細節執行緒
- 04流程控制 for迴圈,while迴圈While
- Python的for迴圈退出Python
- kotlin的迴圈使用Kotlin
- [20190523]修改引數後一些細節注意.txt
- 第 7 節:流程控制-迴圈練習-跳出語句
- 在 Spark 資料匯入中的一些實踐細節Spark
- 11C++迴圈結構-for迴圈(1)C++
- for迴圈、break和continue、二重迴圈