一. 可迭代對象(Iterable) 和迭代器(Iterator)
Python中任意的對象, 只要它定義了可以返回一個迭代器的iter方法, 或者定義了可以支持下標索引的getitem方法, 那麼它就是一個可迭代對象. 簡單說, 可迭代對象就是能提供迭代器的任意對象. 那迭代器又是什麼呢?
任意對象, 只要定義了next(Python2) 或者next方法, 它就是一個迭代器. 就這麼簡單.
現在我們來理解迭代(iteration). 當我們使用一個循環來遍歷某個東西時, 這個過程本身就叫迭代.
二. 生成器(Generators)
1. 生成器基本概念
生成器也是一種迭代器, 但是你只能對其迭代一次. 這是因爲它們並沒有把所有的值存在內存中, 而是在運行時生成值. 你通過遍歷來使用它們, 要麼用一個“for”循環, 要麼將它們傳遞給任意可以進行迭代的函數和結構. 大多數時候生成器是以函數來實現的. 然而, 它們並不返回一個值, 而是yield一個值. 這裏有個生成器函數的簡單例子:
def generator_function():
for i in range(10):
yield i
for item in generator_function():
print(item)
輸出:
0
1
2
3
4
5
6
7
8
9
這個案例並不是非常實用. 生成器最佳應用場景是:你不想同一時間將所有計算出來的大量結果集分配到內存當中, 特別是結果集裏還包含循環. 這樣做會消耗大量資源. 許多Python 2裏的標準庫函數都會返回列表, 而Python 3都修改成了返回生成器, 因爲生成器佔用更少的資源.
下面是一個計算斐波那契數列的生成器:
# generator version
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
for x in fibon(100):
print(x)
用這種方式, 我們可以不用擔心它會使用大量資源. 然而, 之前如果我們這樣來實現的話:
def fibon(n):
a = b = 1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
2. 理解生成器的”生成”
在測試前你需要再知道一個Python內置函數:next(). 它允許我們獲取一個序列的下一個元素. 那我們來驗證下我們的理解:
def generator_function():
for i in range(3):
yield i
gen = generator_function()
print(next(gen)) # Output: 0
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Error
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
我們可以看到, 在yield掉所有的值後, next()觸發了一個StopIteration的異常. 基本上這個異常告訴我們, 所有的值都已經被yield完了. 你也許會奇怪, 爲什麼我們在使用for循環時沒有這個異常呢?答案很簡單. for循環會自動捕捉到這個異常並停止調用next().
3. 內置數據類型中的可迭代對象
my_string = "Yasoob"
print(next(my_string))
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator
這個異常說那個str對象不是一個迭代器. 對, 就是這樣!它是一個可迭代對象, 而不是一個迭代器. 這意味着它支持迭代, 但我們不能直接對其進行迭代操作. 那我們怎樣才能對它實施迭代呢?是時候學習下另一個內置函數, iter. 它將根據一個可迭代對象返回一個迭代器對象. 這裏是我們如何使用它:
my_iter = iter("Yasoob")
print(next(my_iter))
# Output: 'Y'