Python Iterator and Generator
Python Iterator and Generator
Iterator
迭代器(Iterator)和可迭代物件(Iterable)往往是繫結的。可迭代物件就是我們平時經常用的list
,string
, tuple
這種。事實上迭代器的概念會比可迭代物件寬泛很多,一會舉幾個例子就能明白。
在使用list
這種資料型別的時候,我們經常會使用下面這種迭代方式:
# eg 1
mylist = [1,2,3,4]
for x in mylist:
print(x)
>>>1
>>>2
>>>3
>>>4
有時候會很奇怪for迴圈為什麼可以這麼用,列表的索引竟然會自動往後一個一個走,走到結尾了,還會自動停下來,不會有list out of range
要建立一個迭代器,就必須要實現兩個方法,分別是__iter__()
和__next__()
,以及實現異常機制StopIteration
。請看下面的例子:
# eg 2 class PowTwo: """Class to implement an iterator of powers of two""" def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n <= self.max: result = 2 ** self.n self.n += 1 return result else: raise StopIteration
可以看到,迭代器是通過寫一個類來定義的,類裡面實現了剛剛所說的__iter__()
和__next__()
方法。這個迭代器是用來產生一系列2的指數次方的數字的,具體用法看下面:
a = PowTwo(4) i = iter(a) # attention please next(i) >>>1 next(i) >>>2 next(i) >>>4 next(i) >>>8 next(i) >>>16 next(i) >>>Traceback (most recent call last): ... StopIteration
仔細看哦,第一行程式碼用4建立了一個例項a
,設定了這個迭代器的迭代上限,然後並不是直接用這個例項就可以了,還得呼叫iter()
函式去把a
徹底進化成一個Iterator
,進而有了接下來next()
函式的閃亮登場。其實我也蠻奇怪,直接用這個類不好麼,比如下面的程式碼:
a = PowTwo(4)
a.__iter__()
a.__next__()
>>>1
a.__next__()
>>>2
a.__next__()
>>>4
a.__next__()
>>>8
a.__next__()
>>>16
next(i)
>>>Traceback (most recent call last):
...
StopIteration
完全沒問題,但是你自己比較一下二者的程式碼,哪個美一點毋庸置疑。。。再說了,跟裝飾器一樣,一切為了簡潔優美而生嘛。
OK,可以回到最初的起點——for迴圈了。示例1中的程式碼,for迴圈到底做了什麼呢,答案是,for迴圈其實是先把mylist
變成迭代器,然後用while迴圈去迭代:
iter_obj = iter(mylist)
while True:
try:
element = next(iter_obj)
except StopIteration:
break
這樣一路解釋過來,應該就不難理解迭代器了。總結一下就是:
- 如何已經有一個可迭代物件,那麼直接用
iter()
函式去初始化它,然後瘋狂next()
即可; - 如果沒有現成的可迭代物件,那就自己寫一個類,類裡面記得實現
__iter__()
和__next__()
方法,以及異常機制StopIteration
,然後操作同1; - 如果你想要一個無限迭代器,不要實現異常機制
StopIteration
即可。
Generator
這玩意兒就比迭代器複雜點了,所以還得分幾個小點,逐個擊破。
1. 生成器是啥
生成器也是Python中面向一系列需要迭代的問題,常用的解決方案。既然有了迭代器,可以解決很多迭代的問題,那為啥子還要生成器勒?
主要的原因是迭代器的開銷太大了。對於一些小問題還好,大的問題需要迭代的元素很龐大的時候,迭代器就使不上勁兒了。而且,建立一個迭代器,說實話也還挺麻煩的,看看上面的小總結的第二點,你得實現這些方法和手動處理異常。
而且迭代器要寫類,其實一個函式可以搞定的事情,何必那麼複雜。正是應了這個景,生成器也就是在函式物件上搞事情的。
這個原因點到即止,先把生成器講清楚了,自然就通透了。先看一個小例子,寫一個生成器函式(Generator Function):
# eg 1
def my_gen():
n = 1
print('This is printed first')
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
上面就是一個簡單的生成器函式,多了一種關鍵字yield
,細心看會發現這個函式竟然沒有return
!再細看程式碼,沒錯,yield
替代了return
。那怎麼用呢,有兩種用法如下:
# usage 1
a = my_gen()
next(a)
>>>This is printed first
1
next(a)
>>>This is printed second
2
next(a)
>>>This is printed at last
3
next(a)
>>>Traceback (most recent call last):
...
StopIteration
# usage 2
for item in my_gen():
print(item)
>>>
This is printed first
1
This is printed second
2
This is printed at last
3
對於用法1,把函式賦值給了a
,然乎瘋狂next()
即可。你會發現,我們並沒有像迭代器那樣實現__iter__()
和__next__()
方法,以及異常機制StopIteration
,只是用了一個yield
關鍵字,這個生成器函式卻達到了迭代器一樣的效果。
對於用法2,更牛皮了,甚至不用賦值的操作,直接for這個生成器函式。。。
2. 迴圈機制生成器
剛剛那個小函式,就是一個最普通的例子,那問題是如果有多個n想要玩,豈不是來多少手動寫多少?那當然還有迴圈的玩法,帶有迴圈機制的生成器。下面是一個逆序輸出的小例子:
# eg 2
def rev_str(my_str):
length = len(my_str)
for i in range(length - 1,-1,-1):
yield my_str[i]
for char in rev_str("hello"):
print(char)
>>>o
>>>l
>>>l
>>>e
>>>h
沒錯,真無聊,犯得著逆序輸出的程式還得上生成器麼,犯不著,但是,只是想給出這個迴圈機制生成器的概念。如果你發現這個rev_str()
函式和普通的逆序函式完全一樣,只是return
換成了yield
,那就萬事大吉,要理解的就是這個。就這樣一記簡單的替換操作,你就得到了一個生成器,是不是比迭代器省事兒多了。
3. 生成器表示式(Generator Expression)
不知道你有沒有用過列表生成式,沒用過也應該看到過,這類似於匿名函式,語法簡潔,比如:
# eg 3
my_list = [1, 3, 6, 10]
[x**2 for x in my_list]
>>>[1, 9, 36, 100]
生成器表示式和這個幾乎一樣,不信你看:
# eg 4
my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
next(a)
>>>1
next(a)
>>>9
next(a)
>>>36
next(a)
>>>100
把列表生成式的[]
直接改成()
,就得到了一個生成器。
4. Why Generator???
(1). 簡潔
回到最開始迭代器的那個類的例子,用生成器咋寫呢?
def PowTwoGen(max = 0):
n = 0
while n < max:
yield 2 ** n
n += 1
簡潔吧,然後你就可以用這個生成器函式遨遊了。
(2). 開銷小
同樣的一個需要迭代的功能,如果用普通函式寫,一旦需要迭代的元素特別多,在使用的時候,普通函式需要等所有的元素計算出來了,然後把返回值給你。生成器就不是了,它一次計算出一個,用的時候就取一個,並且它還會記住位置,下次用就計算下一個,這樣對空間的開銷也是很小的。
(3). 無限
看下面的函式:
def all_even():
n = 0
while True:
yield n
n += 2
寫普通函式,你必然做不到寫出一個可以無限操作的函式,生成器卻可以。(迭代器也可以,就是麻煩點兒)
5. 再給一個例子
# 利用yield生成一個斐波那契數列的生成器
def fib(max):
n,a,b=0,0,1
while n<max:
yield b
a,b=b,a+b
n+=1
return 'done' # 要不要這句話都行
f=fib(6)
next(f) # 瘋狂next()它
>>>
1
1
2
3
5
8
Traceback (most recent call last):
...
StopIteration: done