上線不足一年,Twitter 宣佈將停止閱後即焚功能 Fleets
在瞭解什麼是迭代器和生成器之前,我們先來了解一下容器的概念。對於一切皆物件來說,容器就是物件的集合。例如列表、元祖、字典等等都是容器。對於容器,你可以很直觀地想象成多個元素在一起的單元;而不同容器的區別,正是在於內部資料結構的實現方法。然後,你就可以針對不同場景,選擇不同時間和空間複雜度的容器。
所有的容器都是可迭代的。而迭代器就是可以用來遍歷容器中元素的。迭代器(iterator)提供了一個 next 的方法。呼叫這個方法後,你要麼得到這個容器的下一個物件,要麼得到一個 StopIteration 的錯誤。你不需要像列表一樣指定元素的索引,因為字典和集合這樣的容器並沒有索引一說。
對於可迭代的物件,我們可以通過iter() 函式返回一個迭代器,然後在通過next()函式可以實現遍歷。我們來看下面這段程式碼來理解一下。
s = set([1,2,3,4,5]) it = iter(s) print(it.__next__()) print(it.__next__()) print(it.__next__())
它的輸出為1,2,3, 就是一個一個的輸出集合中的元素。
那什麼是生成器呢?我們知道,在迭代器中,如果我們想要列舉它的元素,這些元素需要事先生成。這裡,我們先來看下面這個簡單的樣例。
# 顯示當前 python 程式佔用的記憶體大小 def show_memory(temp): pid = os.getpid() p = psutil.Process(pid) info = p.memory_full_info() memory = info.uss / 1024. / 1024 print('{} memory used: {} MB'.format(temp, memory)) def iterator(): show_memory('initing iterator') list_1 = [i for i in range(10000)] show_memory('after iterator initiated') print(sum(list_1)) show_memory('after sum called') def generator(): show_memory('initing generator') list_2 = (i for i in range(10000)) show_memory('after generator initiated') print(sum(list_2)) show_memory('after sum called') iterator() generator() 輸出: initing iterator memory used: 5.58984375 MB after iterator initiated memory used: 6.0234375 MB 49995000 after sum called memory used: 6.0234375 MB initing generator memory used: 6.0234375 MB after generator initiated memory used: 6.0234375 MB 49995000 after sum called memory used: 6.0234375 MB
在iterator(),通過[i for i in range(10000)]就可以生成一個包含一萬個元素的列表。每個元素在生成後都會儲存到記憶體中,你通過程式碼可以看到,它們佔用了巨量的記憶體,記憶體不夠的話就會出現 OOM 錯誤。不過,我們並不需要在記憶體中同時儲存這麼多東西,比如對元素求和,我們只需要知道每個元素在相加的那一刻是多少就行了,用完就可以扔掉了。於是,生成器的概念應運而生,在你呼叫 next() 函式的時候,才會生成下一個變數。生成器在 Python 的寫法是用小括號括起來,(i for i in range(10000)),即初始化了一個生成器。這樣一來,你可以清晰地看到,生成器並不會像迭代器一樣佔用大量記憶體,只有在被使用的時候才會呼叫。而且生成器在初始化的時候,並不需要執行一次生成操作,相比於iterator,generator()函式節省了一次生成一萬個元素的過程。因此不需要佔用大量記憶體。
好的,瞭解了什麼是生成器和迭代器之後,我們看下面這麼一個例子:
給定一個 list 和一個指定數字,求這個數字在 list 中的位置。下面這段程式碼你應該不陌生,也就是常規做法,列舉每個元素和它的 index,判斷後加入 result,最後返回。
def index(list1, target): result = [] for i, num in enumerate(list1): if num == target: result.append(i) return result print(index([2, 3, 6,7,9,0,2,6], 6)) 輸出:[2, 7]
那麼使用迭代器可以怎麼做呢? 如下所示:
def index(list1, target): for i, num in enumerate(list1): if num == target: yield i print(list(index([2, 3, 6,7,9,0,2,6], 6))) 輸出:[2, 7]
上面index函式返回的是一個生成器物件,需要轉換成list後再print輸出。
下面我們來總結一下:1.容器是可迭代物件,可迭代物件呼叫 iter() 函式,可以得到一個迭代器。迭代器可以通過 next() 函式來得到下一個元素,從而支援遍歷。
生成器是一種特殊的迭代器。使用生成器,你可以寫出來更加清晰的程式碼;合理使用生成器,可以降低記憶體佔用、提高程式速度。
有什麼問題,歡迎留言和我討論。更多硬核內容,請關注公眾號。