迭代器與生成器

qq_34157140發表於2020-10-31

什麼是迭代器

本文參考了部分大佬的文章哦:零基礎學python
迭代器就是一個可以讓程式設計師遍歷(特別是列表)的物件,然而一個迭代器在遍歷並讀取一個容器的資料元素時,並不會執行一個迭代。我們先來理解這三方面內容:

  1. 可迭代物件
  2. 迭代器
  3. 迭代

可迭代物件

只要一個物件定義了可以返回一個迭代器的__iter__方法,或是定義了支援下標索引的__getitem__方法,那麼他就是一個可迭代物件
那些型別是list、tuple、file、dict物件有__iter__()方法,標著他們能夠迭代。
如何判斷一個物件是不是可迭代物件

"""
我們已經知道可以對list、tuple、str等型別的資料使⽤for...in...的迴圈語法從其中依次拿到資料進⾏使
⽤,我們把這樣的過程稱為遍歷,也叫迭代isinstance
"""
# 可迭代物件的本質就是:
# ⼀個物件所屬的類中含有 __iter__() ⽅法,該物件就是⼀個可迭代物件
from collections.abc import Iterable

print(isinstance([1, 2, 3], Iterable))


class MyClass():
    # 該魔術方法就是一個迭代器
    def __iter__(self):
        pass


obj = MyClass()
print(isinstance(obj, Iterable))
print(isinstance(10, Iterable))



True
True
False

Process finished with exit code 0

迭代器

只要一個物件,定義了__next__方法,那麼他就是一個迭代器

迭代

就是從某個地方(比如列表)取出一個元素的過程。當我們使用一個迴圈來遍歷某個東西的時候,這個過程就是迭代

使用迭代器自定義列表

# 自定義列表
# 全域性變數要在初始化方法中進行定義

from collections.abc import Iterable


# 自定義陣列類
class MyList(object):
    def __init__(self):
        self.items = []

    def __iter__(self):
        # 可迭代就必須返回迭代器物件,此時我們已新建迭代器類,進行例項化物件即可
        mylistiterator = MyListIterator(self.items)
        return mylistiterator

    def addItem(self, data):
        # 追加儲存資料
        self.items.append(data)


# 自定義迭代器類
class MyListIterator(object):
    def __init__(self, item):
        self.mylist = item
        self.current_index = 0

    def __iter__(self):
        pass

    def __next__(self):
        # 獲取下一個元素時進行判斷,是否下標越界
        if self.current_index < len(self.mylist):
            data = self.mylist[self.current_index]
            self.current_index += 1
            return data
            # 先return後面的程式碼就不執行啦
        else:
            raise StopIteration


if __name__ == '__main__':
    mylist = MyList()
    mylist.addItem("張飛")
    mylist.addItem('關羽')
    mylist.addItem('劉備')
    for value in mylist:
        print(value)
    print(isinstance(mylist, Iterable))


張飛
關羽
劉備
True

Process finished with exit code 0

迭代器案例(斐波那契數列)

class Fibonacci(object):
    # 這是一個迭代器類,有兩個方法iter和next
    def __init__(self, num):
        self.num = num
        self.a = 1
        self.b = 1
        self.current_index = 0

    def __iter__(self):
        # 返回迭代器物件,通常是他自己
        return self

    def __next__(self):
        # 在生成下一個數之前進行判斷。
        if self.current_index <= self.num:
            self.a, self.b = self.b, self.a + self.b
            self.current_index += 1
            return self.a
        else:

            raise StopIteration


if __name__ == '__main__':
    fibonacci = Fibonacci(5)
    for value in fibonacci:
        print(value)



1
2
3
5
8
13

Process finished with exit code 0

什麼是生成器

生成器也是一種迭代器,但是你只能對其迭代一次。這是因為它們並沒有把所有的值存在記憶體中,而是在執行時生成值。你通過遍歷來使用它們,要麼用一個“for”迴圈,要麼將它們傳遞給任意可以進行迭代的函式和結構。大多數時候生成器是以函式來實現的。然而,它們並不返回一個值,而是yield(暫且譯作“生出”)一個值。這裡有個生成器函式的簡單例子:

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

for item in generator_function():
    print(item)

# Output: 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9

這個案例並不是非常實用。生成器最佳應用場景是:你不想同一時間將所有計算出來的大量結果集分配到記憶體當中,特別是結果集裡還包含迴圈。
注:這樣做會消耗大量資源

許多Python 2裡的標準庫函式都會返回列表,而Python 3都修改成了返回生成器,因為生成器佔用更少的資源。

下面是一個計算斐波那契數列的生成器:

# generator version
def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b
for x in fibon(1000000):
    print(x)

next函式

def generator_function():
    for i in range(3):
        yield i

gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
#            File "<stdin>", line 1, in <module>
#         StopIteration

我們可以看到,在yield掉所有的值後,next()觸發了一個StopIteration的異常。基本上這個異常告訴我們,所有的值都已經被yield完了。你也許會奇怪,為什麼我們在使用for迴圈時沒有這個異常呢?啊哈,答案很簡單。for迴圈會自動捕捉到這個異常並停止呼叫next()。你知不知道Python中一些內建資料型別也支援迭代哦?我們這就去看看:

my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#    TypeError: str object is not an iterator

好吧,這不是我們預期的。這個異常說那個str物件不是一個迭代器。對,就是這樣!它是一個可迭代物件,而不是一個迭代器。這意味著它支援迭代,但我們不能直接對其進行迭代操作。那我們怎樣才能對它實施迭代呢?是時候學習下另一個內建函式,iter。它將根據一個可迭代物件返回一個迭代器物件。這裡是我們如何使用它:

my_string = "Yasoob"
my_iter = iter(my_string)
next(my_iter)
# Output: 'Y'

生成器案例(斐波那契數列)

def Fibonacci(n):
    a = 1
    b = 1
    current_index = 0
    while current_index < n:
        a, b = b, a + b
        current_index += 1
        xxx = yield a
        # yield返回a的值,儲存程式狀態,回來可繼續執行next將其喚醒
        print('aaaa')
        # 生成器能使用return,結束生成器執行
        if xxx == 1:
            return 'aini'


if __name__ == '__main__':
    fabonacci = Fibonacci(5)
    print(next(fabonacci))
    # for value in fabonacci:
    #     print(value)
    try:
        print(next(fabonacci))
        #send喚醒生成器執行,並將傳遞給xxx
        print(fabonacci.send(1))
    except StopIteration as e:
        print(e.value)


1
aaaa
2
aaaa
aini
Process finished with exit code 0

生成器是一種用普通函式語法定義的迭代器。通過上面的例子可以看出,這個生成器(也是迭代器),在定義過程中並沒有像上節迭代器那樣寫__inter__()和next(),而是隻要用了yield語句,那個普通函式就神奇般地成為了生成器,也就具備了迭代器的功能特性。

相關文章