淺析python迭代器和生成器

2020-10-03 11:00:15

可迭代物件

概念

一個物件(在Python裡面一切都是物件)只要實現了只要實現了__iter__()方法,那麼這個物件就是可迭代物件

常見的可迭代物件

  1. 集合或序列型別(如list、tuple、set、dict、str)或檔案物件
  2. 在類中定義了__iter__()方法的物件,可以被認為是 Iterable物件,但自定義的可迭代物件要能在for迴圈中正確使用,就需要保證__iter__()實現必須是正確的
  3. 在類中實現瞭如果只實現__getitem__()的物件可以通過iter()函數轉化成迭代器但其本身不是可迭代物件。所以當一個物件能夠在for迴圈中執行,但不一定是Iterable物件。

    針對第一點可以使用print(isinstance([], Iterable)) 進行校驗,返回True則是可迭代物件。

    關於第二點我們可以通過print(hasattr([], "__iter__"))返回True知道內部實現了__iter__方法,但是這裡我們需要注意的是要想被for迴圈遍歷,能夠被內建的iter()函數呼叫並轉化成Iterator物件(如下程式碼所示)

from collections import Iterable
class IterObj:
    
    def __iter__(self):
        return self 

print(isinstance([], Iterable))         
it = IterObj()
print(iter(it))

關於 第四點可以根據如下範例理解

from collections import Iterable, Iterator, Generator


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))  # false

print(hasattr(it, "__iter__"))  # false
print(isinstance(iter(it), Iterable))  # true

for i in it:
    print(i)  # 將列印出3、5、7、11、13、17、19

迭代器

概念

迭代器物件要求支援迭代器協定的物件,在Python中,支援迭代器協定就是實現物件的__iter__()和next()方法。其中__iter__()方法返回迭代器物件本身;next()方法返回容器的下一個元素,在結尾時引發StopIteration異常。簡單來說:一個物件實現了__iter__()和__next__()方法,那麼它就是一個迭代器物件。

範例

class IterObj:

    def __init__(self, n):
        self.a = n

        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()
t = IterObj([3, 5, 7, 11, 13, 17, 19])
# 可以通過for遍歷
print([ i for i in t])

# 也可以通過next(因為遍歷到沒有資料時候,丟擲異常所以需要捕捉下)
while True:
    try:
        print(t.__next__())
    except:
        break

區分iterable,iterator與itertion

  • itertion: 就是迭代,一個接一個(one after another),是一個通用的概念,比如一個迴圈遍歷某個陣列。

  • iterable: 這個是可迭代物件,屬於python的名詞,範圍也很廣,可重複迭代,滿足如下其中之一的都是iterable:

    • 可以for迴圈: for i in iterable

    • 可以按index索引的物件,也就是定義了__getitem__方法,比如list,str;

    • 定義了__iter__方法。可以隨意返回。

    • 可以呼叫iter(obj)的物件,並且返回一個iterator

  • iterator迭代器物件,也屬於python的名詞,只能迭代一次。需要滿足如下的迭代器協定

    • 定義了__iter__方法,但是必須返回自身

    • 定義了next方法,在python3.x是__next__用來返回下一個值,並且當沒有資料了,丟擲StopIteration

    • 可以保持當前的狀態

優缺點

迭代器優點:

  1. 提供了一種通用不依賴索引的迭代取值方式
  2. 同一時刻在記憶體中只存在一個值,更節省記憶體

迭代器缺點:

  1. 取值不如按照索引的方式靈活,不能取指定的某一個值,只能往後取,不能往前去
  2. 無法預測迭代器的長度

生成器

概念

生成器就是一種自定義的迭代器,本質為迭代器,但凡函數內包含yield關鍵字,呼叫函數不會執行函數體程式碼,會得到一個返回值,該返回值就是生成器物件。在廖雪峰教學中解釋為:在Python中,這種一邊迴圈一邊計算的機制,稱為生成器(Generator)

注意: 生成器既是可迭代的也是迭代器

定義生成器有兩種方式:

  1. 列表生成器
  2. 使用yield定義生成器函數  (yield是一個語法糖,內部實現了迭代器協定,同時保持狀態可以掛起)

列表生成器

from collections import *

g = (x  for x in range(3)) # 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)) # 1
print(next(g)) # 2

使用yield定義生成器函數

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

# 檢索資料,開始執行
a = gen()
print(list(a))
    
# 呼叫gen()並沒有真實執行函數,而是隻是返回了一個生成器物件
# 執行第一次a.next()時,才真正執行函數,執行到yield一個返回值,然後就會掛起,
# 保持當前的名稱空間等狀態。然後等待下一次的呼叫,從yield的下一行繼續執行。
a = gen()
print(a.__next__())

這裡yield的作用就相當於return,這個函數就是順序地返回[0,9]的之間的自然數,可以通過next()或使用for迴圈來遍歷。當程式遇到yield關鍵字時,這個生成器函數就返回了,直到再次執行了next()函數,它就會從上次函數返回的執行點繼續執行,即yield退出時儲存了函數執行的位置、變數等資訊,再次執行時,就從這個yield退出的地方繼續往下執行。

迭代器和生成器的區別

生成器能做到迭代器能做的所有事,而且因為自動建立了 iter()和 next()方法,生成器顯得特別簡潔,而且生成器也是高效的,使用生成器表示式取代列表解析可以同時節省記憶體。
 

 

 

參考連結

python的迭代器為什麼一定要實現__iter__方法?

Python:range 物件並不是迭代器(英文)

Python:range 物件並不是迭代器

python迭代器與生成器小結

python迭代器和for迴圈區別

Python迭代器,生成器--精華中的精華