Python學習迭代器(Iterator)

konglingbin發表於2024-05-18
一、可迭代的物件(Iterable)
1、定義:可以直接用在迴圈的資料型別,如list,tuple,dict,set,str,還有generator(生成器),
和帶yield的函式,這些直接可以用在迴圈的物件統稱為可迭代物件(Iterable)
from collections import Iterable
print(isinstance([], Iterable))
print(isinstance((), Iterable))
print(isinstance({}, Iterable))
print(isinstance("abc", Iterable))
print(isinstance((x for x in range(10)), Iterable)) # 生成器也是可迭代物件
print(isinstance(10, Iterable)) # 數字不是可迭代物件

二、迭代器(Iterator)

1、定義:生成器不但可以用在迴圈中,還能用next()不斷呼叫返回下一個值,直到爆出StopIteration異常,
2、可迭代物件和迭代器和區別:迭代器和可迭代物件不是同個概念,區別在於是否有next函式(可以用dir(object)來檢視此物件的所有支援的函式)。
print(isinstance((), Iterator))
print(isinstance({}, Iterator))
print(isinstance("abc", Iterator))
print(isinstance((x for x in range(10)), Iterator))  # 生成器是迭代器
print(isinstance(10, Iterator))  # 數字不是迭代器物件

三、可迭代物件與迭代器之間的轉換

1、使用iter()函式將可迭代物件轉換成迭代器。
list1 = iter([1,2,3,4,5])
print(list1.__next__())
print(list1.__next__())
   2、為什麼像list,string,tuple這樣的可迭代物件不是迭代器?
因為這些資料結構是將已經存在的資料賦值他們,而迭代器是惰性計算的程式流,他甚至沒有資料,所以可迭代物件只能表示有限的元素個數,而迭代器甚至可以表示整個自然數集合。

四、python很多功能都有迭代器的影子,舉一些內建迭代器的例子

1、迴圈用到的range(),其實就是一個迭代器,比如range(10000),不是生成一個10000個元素的集合,而內建了next函式,逐個生成。
2、檔案呼叫的時候file.redelines(),是返回一個列表,而 for line in file: 則是迭代器中逐行前進,也是內建了next函式

五、小結:迭代器是一個概念,其實就是生成器的應用。

Python中的迭代器是一種物件,它可以迭代(遍歷)一個可迭代物件(比如列表、元組或字串)的元素。迭代器用於實現迭代器協議,即包含 __iter__() 方法和 __next__() 方法。

迭代器的工作原理是每次呼叫 __next__() 方法時返回可迭代物件的下一個元素,當沒有元素可迭代時,丟擲 StopIteration 異常。

class MyIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __iter__(self):
return self

def __next__(self):
if self.index < len(self.iterable):
result = self.iterable[self.index]
self.index +=1
return result
else:
raise StopIteration

# 建立一個可迭代物件
my_list = [1, 2, 3, 4, 5]

# 建立一個迭代器
my_iterator = MyIterator(my_list)

# 使用迭代器遍歷元素
for item in my_iterator:
print(item)
值得注意的是,當迭代器耗盡後,如果再次使用迭代器來便利,將不會得到任何輸出。
所以總的來說,迭代器是用於遍歷可迭代物件的物件,它實現了迭代器協議,具有 __iter__() 和 __next__() 方法。

可迭代物件
我們看迭代器好像和平時我們使用的列表、字典等資料結構一樣,都可以遍歷,那麼列表、字典等資料結構是迭代器嗎?

不好意思,他們不是迭代器,而是可迭代物件(iterable)。

可迭代物件(iterable)是指具有迭代行為的物件。當我們希望能夠按照一定方式遍歷物件中的元素時,我們可以將該物件稱為可迭代物件。換句話說,可迭代物件是一種提供迭代能力的容器。
可迭代物件的特點是實現了 __iter__() 方法,這個方法返回一個迭代器(iterator)。迭代器是能夠按照一定順序生成下一個元素的物件。

在 Python 中,許多資料結構都是可迭代物件,比如列表、元組、集合、字典等。我們可以使用for迴圈對這些物件進行遍歷。

同時,也可以使用內建的 iter() 函式將可迭代物件轉換為迭代器。迭代器是可迭代物件的一種特殊形式,實現了 __iter__() 和 __next__() 方法。迭代器可以使用 next() 函式來獲取下一個元素,並且在沒有元素可返回時引發 StopIteration 異常。

my_list = [1, 2, 3, 4, 5] # 列表是可迭代物件
for item in my_list:
print(item)

my_iterator = iter(my_list) # 使用iter()函式將列表這個可迭代物件轉換為迭代器
print(next(my_iterator)) # 輸出第一個元素
print(next(my_iterator)) # 輸出第二個元素
在這個示例中,列表 my_list 是可迭代物件,它可以被 for 迴圈遍歷。另外,我們還使用 iter() 函式將 my_list 轉換為迭代器 my_iterator,並使用 next() 函式逐個訪問其中的元素。

所以總的來說,可迭代物件是指具有迭行為的物件,它們實現了 __iter__() 方法。透過for迴圈或 iter() 函式,我們可以遍歷這些物件的元素。

可迭代物件是指實現了 __iter__() 方法的物件,而迭代器是實現了 __iter__() 和 __next__() 方法的物件,這個可以說是它們比較明顯的區別。

for迴圈機制
從上面我們指定,列表、元組、集合、字典等資料結構是可迭代物件,並不是迭代器。而可迭代物件只實現了__iter__()方法,並不具有迭代(也就是返回下一個元素)的功能。那麼很多同學可能就比較奇怪了,我們平時使用for迴圈遍歷這個資料結構的時候,內部是怎麼遍歷的呢?

實際上在 Python 中,for迴圈在內部自動會呼叫 __iter__() 函式將可迭代物件轉換為迭代器。用for迴圈遍歷可迭代物件的實現機制為:

for迴圈首先會呼叫 __iter__() 函式,該函式會將可迭代物件轉換為一個迭代器物件(如果物件本身就是迭代器,則不作轉換)。
接下來,for迴圈會呼叫迭代器物件的 __next__() 方法來獲取下一個元素。如果迭代器物件沒有下一個元素,會丟擲 StopIteration 異常。
for迴圈會自動捕捉 StopIteration 異常,表示已經迭代完所有元素,迴圈將結束。
所以以下兩個方法實際上是等價的。

my_string = "Hello"

for char in my_string:
print(char)
實際上完全等價於:

my_string = "Hello"
my_iterator = iter(my_string)

print(next(my_iterator)) # 輸出:H
print(next(my_iterator)) # 輸出:e
print(next(my_iterator)) # 輸出:l
print(next(my_iterator)) # 輸出:l
print(next(my_iterator)) # 輸出:o
生成器
生成器(Generator)是一種特殊的迭代器,它可以在迭代過程中動態地生成值,而不是一次性地將所有元素放在記憶體中。生成器使用 yield 關鍵字來定義,當生成器的程式碼塊執行到 yield 語句時,就會暫停執行並返回一個值,下次呼叫時會從上次暫停的位置繼續執行。這樣可以在需要的時候生成值,而不是一次性生成所有的值。

def my_generator():
yield 1
yield 2
yield 3

# 使用生成器
gen = my_generator()

print(next(gen)) # 輸出:1
print(next(gen)) # 輸出:2
print(next(gen)) # 輸出:3
print(next(gen)) # 丟擲 StopIteration 異常
在上面的示例中,我們定義了一個名為 my_generator 的生成器函式,它透過使用 yield 關鍵字來產生值。當我們呼叫生成器函式時,它返回一個生成器物件 gen。我們可以使用 next() 函式來逐個獲取生成器的值。每次呼叫 next() 時,生成器函式會從上次暫停的位置繼續執行,並返回 yield 語句的值。當生成器函式執行完畢或沒有更多的值可生成時,呼叫 next() 會丟擲 StopIteration 異常。

生成器的一個重要特點是它們可以節省記憶體,尤其在處理大量資料時非常有用。由於生成器是按需生成值,只有在需要時才會在迭代過程中生成值,不會一次性佔用大量的記憶體。所在遍歷大批次資料的時候,非常有用,因為如果將大批次的資料直接載入到記憶體中在遍歷,肯定會消耗很多記憶體,而利用生成器就可以做到需要哪些遍歷哪些。

如果用簡單一句話來說就是,我既想大量的資料,又想讓它佔用空間少,實現魚和熊掌的兼得,那麼就用生成器!

def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.rstrip()

# 使用生成器遍歷大檔案
file_generator = read_large_file('large_file.txt')
print(file_generator) # 輸出<generator object fibonacci_generator at 0x0000017362DCFED0>
print(type(file_generator)) # 輸出<class 'generator'>
for line in file_generator:
# 處理每一行資料
print(line)
從上面可以看出,read_large_file() 函式是一個生成器函式,它按行從一個大檔案中讀取資料。透過使用 yield 關鍵字,在每次迭代時逐行生成檔案的內容,並將其作為生成器的值返回。然後,我們可以使用 for 迴圈逐行處理大檔案。可以看到,我們列印的file_generator型別是一個生成器。

當然,以上的例子並不一定需要採用生成器才能處理,我們直接在第4行進行處理也是可以的,生成器更多的只是提供一種思路,當你用常規方法不能解決問題的時候,可以試試用生成器。

生成器原理
生成器的原理基於迭代器(iterators)和生成器函式(generator functions)。

def even_numbers(n):
for i in range(1, n+1):
if i % 2 == 0:
yield i

# 建立生成器物件
even_generator = even_numbers(10)

# 列印生成的偶數
for number in even_generator:
print(number)
生成器函式
生成器透過生成器函式建立,生成器函式是一種特殊型別的函式,使用 yield 語句來生成值。像上面的例子even_numbers函式就是要給生成器函式,當呼叫生成器函式時,它返回一個生成器物件,而不是立即執行函式體內的程式碼,even_generator就是一個生成器物件。

而之前我們說過生成器是一種特殊型別的迭代器,它可以在迭代中生成值。迭代器是一個實現了 __iter__() 和 __next__() 方法的物件。__iter__() 方法返回迭代器本身,而for迴圈內部會自動呼叫 __next__() 方法用於獲取下一個值。每次呼叫 __next__() 方法時,生成器會從上一次暫停的位置繼續執行,直到遇到下一個 yield 語句,然後將 yield 後面的值返回給呼叫者。

逐個生成值
生成器在呼叫 __next__() 方法時逐個生成值,並且每次在生成一個值後會暫停執行。這種延遲生成的機制使得生成器能夠處理大量資料或無限序列,而不需要一次性載入或計算所有值。

狀態儲存
生成器在暫停執行時會儲存其狀態,包括區域性變數、指令指標等資訊。下一次呼叫 __next__() 方法時,生成器會從上一次暫停的地方恢復執行,並繼續執行剩餘部分的程式碼。




相關文章