農林漁牧網

您現在的位置是:首頁 > 農業

一文搞懂Python可迭代、迭代器和生成器的概念

2022-03-06由 一個有思想的程式猿 發表于 農業

生成器是什麼意思

關於我

一個有思想的程式猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是我們團隊的主要技術棧。

Github:https://github。com/hylinux1024

微信公眾號:終身開發者(angrycode)

一文搞懂Python可迭代、迭代器和生成器的概念

在 Python中可迭代( Iterable)、迭代器( Iterator)和生成器( Generator)這幾個概念是經常用到的,初學時對這幾個概念也是經常混淆,現在是時候把這幾個概念搞清楚了。

0x00 可迭代(Iterable)

簡單的說,一個物件(在Python裡面一切都是物件)只要實現了只要實現了 __iter__()方法,那麼用 isinstance()函式檢查就是 Iterable物件;

例如

class IterObj: def __iter__(self): # 這裡簡單地返回自身 # 但實際情況可能不會這麼寫 # 而是透過內建的可迭代物件來實現 # 下文的列子中將會展示 return self

上面定義了一個類 IterObj並實現了 __iter__()方法,這個就是一個

可迭代(Iterable)物件

it = IterObj() print(isinstance(it, Iterable)) # true print(isinstance(it, Iterator)) # false print(isinstance(it, Generator)) # false

記住這個類,下文我們還會看到這個類的定義。

常見的可迭代物件

在 Python中有哪些常見的可迭代物件呢?

集合或序列型別(如 list、 tuple、 set、 dict、 str)

檔案物件

在類中定義了 __iter__()方法的物件,可以被認為是 Iterable物件,但自定義的可迭代物件要能在 for迴圈中正確使用,就需要保證 __iter__()實現必須是正確的(即可以透過內建 iter()函式轉成 Iterator物件。關於 Iterator下文還會說明,這裡留下一個坑,只是記住 iter()函式是能夠將一個可迭代物件轉成迭代器物件,然後在 for中使用)

在類中實現瞭如果只實現 __getitem__()的物件可以透過 iter()函式轉化成迭代器但其本身不是可迭代物件。所以當一個物件能夠在 for迴圈中執行,但不一定是 Iterable物件。

關於

第1、2點

我們可以透過以下來驗證

print(isinstance([], Iterable)) # true list 是可迭代的 print(isinstance({}, Iterable)) # true 字典是可迭代的 print(isinstance((), Iterable)) # true 元組是可迭代的 print(isinstance(set(), Iterable)) # true set是可迭代的 print(isinstance(‘’, Iterable)) # true 字串是可迭代的 currPath = os。path。dirname(os。path。abspath(__file__)) with open(currPath+‘/model。py’) as file: print(isinstance(file, Iterable)) # true

我們再來看

第3點

print(hasattr([], “__iter__”)) # true print(hasattr({}, “__iter__”)) # true print(hasattr((), “__iter__”)) # true print(hasattr(‘’, “__iter__”)) # true

這些內建集合或序列物件都有 __iter__屬性,即他們都實現了同名方法。但這個可迭代物件要在 for迴圈中被使用,那麼它就應該能夠被內建的 iter()函式呼叫並轉化成 Iterator物件。

例如,我們看內建的可迭代物件

print(iter([])) # print(iter({})) # print(iter(())) # print(iter(‘’)) #

它們都相應的轉成了對應的迭代器( Iterator)物件。

現在回過頭再看看一開始定義的那個 IterObj類

class IterObj: def __iter__(self): return self it = IterObj()print(iter(it))

我們使用了 iter()函式,這時候將再控制檯上打印出以下資訊:

Traceback (most recent call last): File “/Users/mac/PycharmProjects/iterable_iterator_generator。py”, line 71, in print(iter(it))TypeError: iter() returned non-iterator of type ‘IterObj’

出現了型別錯誤,意思是 iter()函式不能將‘非迭代器’型別轉成迭代器。

那如何才能將一個可迭代( Iterable)物件轉成迭代器( Iterator)物件呢?

我們修改一下 IterObj類的定義

class IterObj: def __init__(self): self。a = [3, 5, 7, 11, 13, 17, 19] def __iter__(self): return iter(self。a)

我們在構造方法中定義了一個名為 a的列表,然後還實現了 __iter__()方法。

修改後的類是可以被 iter()函式呼叫的,即也可以在 for迴圈中使用

it = IterObj() print(isinstance(it, Iterable)) # true print(isinstance(it, Iterator)) # false print(isinstance(it, Generator)) # false print(iter(it)) # for i in it: print(i) # 將列印3、5、7、11、13、17、19元素

因此

在定義一個可迭代物件時,我們要非常注意 __iter__()方法的內部實現邏輯,一般情況下,是透過一些已知的可迭代物件(例如,上文提到的集合、序列、檔案等或其他正確定義的可迭代物件)來輔助我們來實現

關於

第4點

說明的意思是 iter()函式可以將一個實現了 __getitem__()方法的物件轉成迭代器物件,也可以在 for迴圈中使用,但是如果用 isinstance()方法來檢測時,它不是一個可迭代物件。

class IterObj: def __init__(self): self。a = [3, 5, 7, 11, 13, 17, 19] def __getitem__(self, i): return self。a[i] it = IterObj()print(isinstance(it, Iterable)) # falseprint(isinstance(it, Iterator)) # falseprint(isinstance(it, Generator)) falseprint(hasattr(it, “__iter__”)) # falseprint(iter(it)) # for i in it: print(i) # 將打印出3、5、7、11、13、17、19

這個例子說明了可以

在 for中使用的物件,不一定是可迭代物件。

現在我們做個小結:

一個可迭代的物件是實現了 __iter__()方法的物件

它要在 for迴圈中使用,就必須滿足 iter()的呼叫(即呼叫這個函式不會出錯,能夠正確轉成一個 Iterator物件)

可以透過已知的可迭代物件來輔助實現我們自定義的可迭代物件。

一個物件實現了 __getitem__()方法可以透過 iter()函式轉成 Iterator,即可以在 for迴圈中使用,但它不是一個可迭代物件(可用isinstance方法檢測())

0x01 迭代器(Iterator)

上文很多地方都提到了 Iterator,現在我們把這個坑填上。

當我們對可迭代的概念瞭解後,對於迭代器就比較好理解了。

一個物件實現了 __iter__()和 __next__()方法,那麼它就是一個迭代器物件。

例如

class IterObj: def __init__(self): self。a = [3, 5, 7, 11, 13, 17, 19] self。n = len(self。a) self。i = 0 def __iter__(self): return iter(self。a) def __next__(self): while self。i < self。n: v = self。a[self。i] self。i += 1 return v else: self。i = 0 raise StopIteration()

在 IterObj中,建構函式中定義了一個列表 a,列表長度 n,索引 i。

it = IterObj() print(isinstance(it, Iterable)) # true print(isinstance(it, Iterator)) # true print(isinstance(it, Generator)) # false print(hasattr(it, “__iter__”)) # true print(hasattr(it, “__next__”)) # true

我們可以發現上文提到的

集合和序列物件是可迭代的但不是迭代器

print(isinstance([], Iterator)) # false print(isinstance({}, Iterator)) # false print(isinstance((), Iterator)) # false print(isinstance(set(), Iterator)) # false print(isinstance(‘’, Iterator)) # false

檔案物件是迭代器

currPath = os。path。dirname(os。path。abspath(__file__)) with open(currPath+‘/model。py’) as file: print(isinstance(file, Iterator)) # true

一個迭代器( Iterator)物件不僅可以在 for迴圈中使用,還可以透過內建函式 next()函式進行呼叫。

例如

it = IterObj()next(it) # 3next(it) # 5

0x02 生成器(Generator)

現在我們來看看什麼是生成器?

一個生成器既是可迭代的也是迭代器

定義生成器有兩種方式:

列表生成器

使用 yield定義生成器函式

先看第1種情況

g = (x * 2 for x in range(10)) # 0~18的偶數生成器 print(isinstance(g, Iterable)) # true print(isinstance(g, Iterator)) # true print(isinstance(g, Generator)) # true print(hasattr(g, “__iter__”)) # true print(hasattr(g, “__next__”)) # true print(next(g)) # 0 print(next(g)) # 2

列表生成器可以不需要消耗大量的記憶體來生成一個巨大的列表,只有在需要資料的時候才會進行計算。

再看第2種情況

def gen(): for i in range(10): yield i

這裡 yield的作用就相當於 return,這個函式就是順序地返回 [0,10)的之間的自然數,可以透過 next()或使用 for迴圈來遍歷。

當程式遇到 yield關鍵字時,這個生成器函式就返回了,直到再次執行了 next()函式,它就會從上次函式返回的執行點繼續執行,即 yield退出時儲存了函式執行的位置、變數等資訊,再次執行時,就從這個 yield退出的地方繼續往下執行。

在 Python中利用生成器的這些特點可以實現協程。協程可以理解為一個輕量級的執行緒,它相對於執行緒處理高併發場景有很多優勢。

看下面一個用協程實現的

生產者-消費者模型

def producer(c): n = 0 while n < 5: n += 1 print(‘producer {}’。format(n)) r = c。send(n) print(‘consumer return {}’。format(r))def consumer(): r = ‘’ while True: n = yield r if not n: return print(‘consumer {} ’。format(n)) r = ‘ok’if __name__ == ‘__main__’: c = consumer() next(c) # 啟動consumer producer(c)

這段程式碼執行效果如下

producer 1consumer 1 producer return okproducer 2consumer 2 producer return okproducer 3consumer 3 producer return ok

協程實現了 CPU在兩個函式之間進行切換從而實現併發的效果。

0x04 引用

https://docs。python。org/3。7/