Python迭代器和生成器

2021-08-29 03:56:38 字數 4445 閱讀 9814

可迭代物件

在介紹迭代器之前,我們得先引入可迭代物件(iterable)的概念。可迭代物件可以直接作用於for迴圈。例如常見的列表、字典和元組都是可迭代物件。例如我們可以使用for迴圈依次輸出列表的值。

x=[1,2,3,4]

for i in x:

print(i)

事實上,只要物件實現了__iter__()方法,我們就可以對該物件進行迭代。該方法返回了乙個迭代器物件。

迭代器

迭代器(iterator)就是實現了__next__()方法的物件,當呼叫該方法時,迭代器會返回它的下乙個值。可迭代物件不一定是迭代器,如list、dict等,它們雖然可迭代,但是並不能稱作迭代器。而迭代器是可迭代物件。當然,我們也可以通過iter()方法獲得乙個迭代器。

x=[1,2,3,4]

y=iter(x)

y.__next__()

既然列表等也可進行迭代,為什麼還要引入迭代器的概念呢?這是因為使用列表等可迭代物件是一次性獲取所有的值。當列表中有100萬個值時,就會占用十分大的記憶體。而迭代器可以乙個乙個的獲取值。我們以生成斐波那契數列為例,寫乙個生成該數列的迭代器。

class fib:

def __init__(self):

self.prev=0

self.curr=1

def __iter__(self):

return self

def __next__(self):

value=self.curr

self.curr+=self.prev

self.prev=value

return value

f=fib()

for i in range(5):

print(f.__next__())

fib既是乙個可迭代物件,因為它實現了__iter__()方法,同時它又是乙個迭代器,因為它實現了__next__()方法。當我們需要值時,呼叫__next__()方法向呼叫者返回結果,但沒有呼叫時它便處於休眠狀態。

生成器(generator)

生成器是python新引入的概念,它是一種用普通的函式語法定義的迭代器。注意,生成器也是一種迭代器。它比迭代器更加優雅。生成器的實現是採用了yield的關鍵字,任何包含yield語句的函式稱為生成器。我們還是用乙個例子來說明生成器的用法。

def fib():

prev,curr=0,1

while true:

yield curr

prev,curr=curr,curr+prev

f=fib()

print(f)

for i in range(5):

print(f.__next__())

fib函式看樣子就像是乙個普通函式,除了它沒有使用return關鍵字,而是使用了yield。呼叫該函式後返回的是乙個生成器物件。此時函式體內的**並沒有執行,只有當呼叫了__next__()後才執行**。返回的生成器結果如下所示。

一般的函式執行完後會return乙個值然後退出,而生成器遇到yield關鍵字後會向呼叫者返回乙個值,並且自動掛起,同時還保留著當前的狀態,今後需要時將呼叫send()或__next__()喚醒並繼續執行。每乙個生成器都有__next__()方法,它要麼返回迭代的下一項,要麼引起異常結束迭代。

除了__next__()方法之外,還有send()也可以喚醒程式並繼續執行,與__next__()不同的是,它傳送乙個資訊到生成器內部。下面依舊通過乙個例子講解send()和__next__()的執行過程。

這裡參照了 的例子,寫的十分詳細。

def gen():

print("start") #*0

m = yield 2 #*1 # yield意思是產生, 此表示產生2 就是呼叫可以返回2 (當然,返回是有條件的

print(m) #*2

n = yield 3 #*3

print(n) #*4

try: #*5

print("end") #*6

except stopiteration as e: #*7

print(e) #*8

# raise e

\# 先說明yield表示式, yield有點像《前置操作符》, 跟在表示式前面,用於把表示式的值返回給

\#呼叫者, (yield 2)本身的值賦給m

g = gen()

""" result:

"""\#↑>>> 可以看出是乙個generator, 此時並沒有執行 *0和*1行

\#g.send(3)

""" result:

typeerror: can't send non-none value to a just-started generator

"""\#↑>>> 開始生成器不能send非空值

print("--------------")

g.send(none) # 住: g.__next__()相當於g.send(none)

""" result:

start

2 #此時函式(生成器)返回了2(就是yield後的,此時你可以理解為yield相當於return,但是返回後暫停了執行)

"""\# 此時函式掛起(讓出cpu使用權)並保持狀態,直到遇到下乙個__next__/send

\# g.send(none) 呼叫者把none這個訊息send給上乙個yield表示式,由於第一次呼叫,沒有上乙個yield.

\# 到目前為止, 呼叫了一次send(none) 總結一下, 上面說到保持狀態:目前函式執行到*1行並掛起

print("--------------")

g.send(5)

print("--------------")

""" result:

53 #和上面一樣,第二個yield生成(返回)的值為3

"""\# 此時函式從*1行後繼續執行 通過呼叫g.send(5),呼叫者把5這個訊息send給上乙個yield表示式(yield 2)

\#既:(yield 2)這個整體的值,此時m = 5,到目前為止, 呼叫了一次send(none),一次send(5) 總結一下, 上面

\#說到保持狀態:1.此時的狀態是:m(既yield 2這個整體的結果值)被呼叫者傳送訊息賦值為5 2.目前函式執行

\#到*3行並掛起

print(g.send(6))

\#result:

\# 6

\# end

\# stopiteration

\# 此時函式從*3行後繼續執行 通過呼叫g.send(6),呼叫者把6這個訊息send給了上乙個yield表示式(yield 3)

\#既:(yield 3)這個整體的值,此時n = 6,繼續往下執行,由於沒有了遇不到yield,所有會出現 stopiteration

\#異常而結束。到目前為止,生成器函式執行結束,由於生成器會記住狀態, 再呼叫send/__next__還會出現stopiteration異常

\# 通過生成器, 呼叫者和函式內部可以互動訊息, 暫停執行, 可用同步**處理非同步, 如:tornado的 @gen修飾器,

\#twisted的@inlinecallback修飾器...

寫到這,還有乙個問題需要明確,那麼為什麼list等是可迭代物件,卻不是迭代器呢?

這是因為python的iterator物件表示的是乙個資料流,iterator物件可以被__next__()函式呼叫並不斷返回下乙個資料,直到沒有資料時丟擲stopiteration錯誤。可以把這個資料流看做是乙個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下乙個資料,所以iterator的計算是惰性的,只有在需要返回下乙個資料時它才會計算。

總結

(1)生成器(generator)能夠迭代的關鍵是他有__next__()方法,工作原理就是通過重複呼叫__next__()方法,直到捕獲乙個異常。

(3)send()和__next__()的區別就在於send可傳遞引數給yield表示式,這時候傳遞的引數就會作為yield表示式的值,而yield的引數是返回給呼叫者的值,也就是說send可以強行修改上乙個yield表示式值。

(4)第一次呼叫時候必須先__next__()或send(),否則會報錯,send後之所以為none是因為這時候沒有上乙個yield,所以也可以認為__next__()等同於send(none)

Python迭代器和生成器

先說迭代器,對於string list dict tuple等這類容器物件,使用for迴圈遍歷是很方便的。在後台for語句對容器物件呼叫iter 函式,iter 是python的內建函式。iter 會返回乙個定義了next 方法的迭代器物件,它在容器中逐個訪問容器內元素,next 也是python的...

Python迭代器和生成器

迭代器是訪問集合元素的一種方法 是可以記住遍歷的位置的物件。迭代器物件從集合的第乙個元素開始訪問,直到所有的元素被訪問 他有兩個基本的方法,iter 和next 字串,列表或遠足物件都可以用於建立迭代器 list1 1,2,3,4 it1 iter list1 建立迭代器物件 print next ...

python 迭代器和生成器

迭代器是訪問集合元素的一種方式。迭代器物件從集合的第乙個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會後退,不過這也沒什麼,因為人們很少在迭代途中往後退。另外,迭代器的一大優點是不要求事先準備好整個迭代過程中所有的元素。迭代器僅僅在迭代到某個元素時才計算該元素,而在這之前或之後,元素可...