廖雪峰 | 4.2 生成器和迭代器

生成器

1,生成器定義
一邊循環(huán)一邊計(jì)算的機(jī)制,稱為生成器,generator。generator保存的是算法。
2,生成器的創(chuàng)建(法一)
(1)把一個(gè)列表生成式的[]改成()

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

(2)打印generator的元素:next()for循環(huán)

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

一般不會(huì)調(diào)用next(),而是通過for循環(huán)來迭代它,并且不需要關(guān)心StopIteration的錯(cuò)誤。
3,生成器的創(chuàng)建(方法二)
如果推算的算法比較復(fù)雜,用類似列表生成式的for循環(huán)無法實(shí)現(xiàn)的時(shí)候,還可以用函數(shù)來實(shí)現(xiàn)。
(1)斐波拉契數(shù)列(Fibonacci)的fib函數(shù)
1, 1, 2, 3, 5, 8, 13, 21, 34, ...:除第一個(gè)和第二個(gè)數(shù)外,任意一個(gè)數(shù)都可由前兩個(gè)數(shù)相加得到:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

(2)斐波拉契數(shù)列(Fibonacci)的generator函數(shù)
只需要把fib函數(shù)中的print(b)改為yield b就可以

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
  • 生成器定義:如果一個(gè)函數(shù)定義中包含yield關(guān)鍵字,那么這個(gè)函數(shù)就不再是一個(gè)普通函數(shù),而是一個(gè)generator函數(shù),調(diào)用一個(gè)generator函數(shù)將返回一個(gè)generator
  • generator函數(shù)不等于generator
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

(3)注意:調(diào)用generator函數(shù)會(huì)創(chuàng)建一個(gè)generator對象,多次調(diào)用generator函數(shù)會(huì)創(chuàng)建多個(gè)相互獨(dú)立的generator
例:定義一個(gè)generator函數(shù),依次返回?cái)?shù)字1,3,5:

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)

調(diào)用該generator函數(shù)時(shí),首先要生成一個(gè)generator對象,然后用next()函數(shù)不斷獲得下一個(gè)返回值:

>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

odd不是普通函數(shù),而是generator函數(shù),在執(zhí)行過程中,遇到yield就中斷,下次又繼續(xù)執(zhí)行。執(zhí)行3次yield后,已經(jīng)沒有yield可以執(zhí)行了,所以,第4次調(diào)用next(o)就報(bào)錯(cuò)。
然而,

>>> next(odd())
step 1
1
>>> next(odd())
step 1
1
>>> next(odd())
step 1
1

原因在于odd()會(huì)創(chuàng)建一個(gè)新的generator對象,上述代碼實(shí)際上創(chuàng)建了3個(gè)完全獨(dú)立的generator,對3個(gè)generator分別調(diào)用next()當(dāng)然每個(gè)都會(huì)返回第一個(gè)值。
4,練習(xí)
楊輝三角定義如下:

楊輝三角

把每一行看做一個(gè)list,試寫一個(gè)generator,不斷輸出下一行的list:

# -*- coding: utf-8 -*-
def triangles():
  L = [1]
  while True:
    yield L
    L = [1] + [L[n] + L[n + 1] for n in range(len(L) - 1)] + [1]

#測試生成器輸出
# 期待輸出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
n = 0
results = []
for t in triangles():
    results.append(t)
    n = n + 1
    if n == 10:
        break

for t in results:
    print(t)

if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('測試通過!')
else:
    print('測試失敗!')
 Run

迭代器

1,IterableIterator
(1)可以直接作用于for循環(huán)的對象統(tǒng)稱為可迭代對象:Iterable。
可以被next()函數(shù)調(diào)用并不斷返回下一個(gè)值的對象稱為迭代器:Iterator。
(2)可以使用isinstance()判斷一個(gè)對象是否是Iterable對象:

>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

也可以使用isinstance()判斷一個(gè)對象是否是Iterator對象:

>>> from collections.abc import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

2,IterableIterator轉(zhuǎn)換
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。
listdict、strIterable變成Iterator可以使用iter()函數(shù):

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

3,為什么listdictstr等數(shù)據(jù)類型不是Iterator?
答:這是因?yàn)镻ython的Iterator對象表示的是一個(gè)數(shù)據(jù)流,Iterator對象可以被next()函數(shù)調(diào)用并不斷返回下一個(gè)數(shù)據(jù),直到?jīng)]有數(shù)據(jù)時(shí)拋出StopIteration錯(cuò)誤??梢园堰@個(gè)數(shù)據(jù)流看做是一個(gè)有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數(shù)實(shí)現(xiàn)按需計(jì)算下一個(gè)數(shù)據(jù),所以Iterator的計(jì)算是惰性的,只有在需要返回下一個(gè)數(shù)據(jù)時(shí)它才會(huì)計(jì)算。
Iterator甚至可以表示一個(gè)無限大的數(shù)據(jù)流,例如全體自然數(shù)。而使用list是永遠(yuǎn)不可能存儲全體自然數(shù)的。
4,小結(jié)
凡是可作用于for循環(huán)的對象都是Iterable類型;
凡是可作用于next()函數(shù)的對象都是Iterator類型,它們表示一個(gè)惰性計(jì)算的序列;
集合數(shù)據(jù)類型如listdict、str等是Iterable但不是Iterator,不過可以通過iter()函數(shù)獲得一個(gè)Iterator對象。
Python的for循環(huán)本質(zhì)上就是通過不斷調(diào)用next()函數(shù)實(shí)現(xiàn)的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

實(shí)際上完全等價(jià)于:

# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環(huán):
while True:
    try:
        # 獲得下一個(gè)值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循環(huán)
        break
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容