Python三大神器之迭代器,生成器

迭代器

迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會後退。

可迭代對象

我們已經知道可以對list、tuple、str等類型的數據使用for...in...的循環語法從其中依次拿到數據進行使用,我們把這樣的過程稱爲遍歷,也叫迭代

我們把可以通過for...in...這類語句迭代讀取一條數據供我們使用的對象稱之爲可迭代對象(Iterable)**。

但是,是否所有的數據類型都可以放到for...in...的語句中,然後讓for...in...每次從中取出一條數據供我們使用,即供我們迭代嗎?

# for循環可迭代的對象有: list tuple str dict set range
# 列表可以迭代
for value in [1,2,3]:
    print(value)

print(20*"--")
# 元組可以迭代
for value in (1,2,3):
    print(value)

如何判斷一個對象是否可以迭代

可以使用 isinstance() 判斷一個對象是否是 Iterable 對象:

from collections import Iterable
# 使用isinstance() 函數檢測某個對象是否是一個可迭代的對象

# 列表是可迭代對象
result = isinstance([1,2], Iterable)
print(result)

可迭代對象的本質

我們分析對可迭代對象進行迭代使用的過程,發現每迭代一次(即在for...in...中每循環一次)都會返回對象中的下一條數據,一直向後讀取數據直到迭代了所有數據後結束。那麼,在這個過程中就應該有一個“人”去記錄每次訪問到了第幾條數據,以便每次迭代都可以返回下一條數據。我們把這個能幫助我們進行數據迭代的“人”稱爲迭代器(Iterator)

可迭代對象的本質就是可以向我們提供一個這樣的中間“人”即迭代器幫助我們對其進行迭代遍歷使用。

可迭代對象通過__iter__方法向我們提供一個迭代器,我們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,然後通過這個迭代器來依次獲取對象中的每一個數據.

那麼也就是說,一個具備了__iter__方法的對象,就是一個可迭代對象。

from collections import Iterable
# 使用isinstance() 函數檢測某個對象是否是一個可迭代的對象


class MyClass(object):
    # 可迭代對象的本質是,類中是否定義了 __iter__() 方法
    def __iter__(self):
        return self


c1 = MyClass()
# 對象c1不是可迭代對象
result = isinstance(c1, Iterable)
print(result)

 iter()函數與next()函數

list、tuple等都是可迭代對象,我們可以通過iter()函數獲取這些可迭代對象的迭代器。然後我們可以對獲取到的迭代器不斷使用next()函數來獲取下一條數據。iter()函數實際上就是調用了可迭代對象的__iter__方法。

注意,當我們已經迭代完最後一個數據之後,再次調用next()函數會拋出StopIteration的異常,來告訴我們所有數據都已迭代完成,不用再執行next()函數了。

>>> li = [11, 22, 33, 44, 55]
>>> li_iter = iter(li)
>>> next(li_iter)
11
>>> next(li_iter)
22
>>> next(li_iter)
33
>>> next(li_iter)
44
>>> next(li_iter)
55
>>> next(li_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

迭代器Iterator

通過上面的分析,我們已經知道,迭代器是用來幫助我們記錄每次迭代訪問到的位置,當我們對迭代器使用next()函數的時候,迭代器會向我們返回它所記錄位置的下一個位置的數據。實際上,在使用next()函數的時候,調用的就是迭代器對象的__next__方法(Python3中是對象的__next__方法,Python2中是對象的next()方法)。所以,我們要想構造一個迭代器,就要實現它的__next__方法。但這還不夠,python要求迭代器本身也是可迭代的,所以我們還要爲迭代器實現__iter__方法,而__iter__方法要返回一個迭代器,迭代器自身正是一個迭代器,所以迭代器的__iter__方法返回自身即可。

一個實現了__iter__方法和__next__方法的對象,就是迭代器。

下面我們寫個自定義的迭代器:

from collections import Iterable
from collections import Iterator


class StudentList(object):

    def __init__(self):
        # 創建列表
        self.items = list()

    def addItem(self,item):
        # 追加元素到列表中
        self.items.append(item)

    def __iter__(self):

        # 創建迭代器對象
        studentIterator = StudentIterator(self.items)

        # 返回迭代器對象
        return studentIterator


# 定義迭代器
class StudentIterator(object):
    # 定義構造方法
    # 1)完成 索引下標定義和初始化
    # 2)接收要遍歷的列表值

    def __init__(self, items):

        self.items = items
        self.current_index = 0

    def __iter__(self):
        return self

    def __next__(self):
        # 判斷位置是否合法
        if self.current_index < len(self.items):
            # 根據current_index 返回列表值
            item = self.items[self.current_index]
            # 讓 下標+1
            self.current_index += 1
            # 返回元素內容
            return item

        else:
            # 停止迭代
            raise StopIteration

# 實例化對象
stulist = StudentList()

stulist.addItem("年華")
stulist.addItem("終歸")
stulist.addItem("時光")
# 檢查是否是可迭代對象
result = isinstance(stulist, Iterable)
print(result)

for value in stulist:
    print(value)

並不是只有for循環能接收可迭代對象

除了for循環能接收可迭代對象,list、tuple等也能接收。

li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)

生成器

利用迭代器,我們可以在每次迭代獲取數據(通過next()方法)時按照特定的規律進行生成。但是我們在實現一個迭代器時,關於當前迭代到的狀態需要我們自己記錄,進而才能根據當前狀態生成下一個數據。爲了達到記錄當前狀態,並配合next()函數進行迭代使用,我們可以採用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器

創建生成器方法1

要創建一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改成 ( )創建 L 和 G 的區別僅在於最外層的 [ ] 和 ( ) , L 是一個列表,而 G 是一個生成器。我們可以直接打印出列表L的每一個元素,而對於生成器G,我們可以按照迭代器的使用方法來使用,即可以通過next()函數、for循環、list()等方法使用。


In [15]: L = [ x*2 for x in range(5)]

In [16]: L
Out[16]: [0, 2, 4, 6, 8]

In [17]: G = ( x*2 for x in range(5))

In [18]: G
Out[18]: <generator object <genexpr> at 0x7f626c132db0>

創建生成器方法2

generator非常強大。如果推算的算法比較複雜,用類似列表生成式的 for 循環無法實現的時候,還可以用函數來實現。

生成器 中使用 return問題:

生成器客戶以使用return 關鍵字,語法上沒有問題,但是如果執行到 return 語句以後,生成器會停止迭代,拋出停止迭代的異常


In [30]: def fib(n):
   ....:     current = 0
   ....:     num1, num2 = 0, 1
   ....:     while current < n:
   ....:         num = num1
   ....:         num1, num2 = num2, num1+num2
   ....:         current += 1
   ....:         yield num
   ....:     return 'done'
   ....:

In [31]: F = fib(5)

In [32]: next(F)
Out[32]: 1

StopIteration: done

在使用生成器實現的方式中,我們將原本在迭代器__next__方法中實現的基本邏輯放到一個函數中來實現,但是將每次迭代返回數值的return換成了yield,此時新定義的函數便不再是函數,而是一個生成器了。簡單來說:只要在def中有yield關鍵字的 就稱爲 生成器.

注:yield 在這裏相當於暫停的功能。

 ....省略代碼....    

    while current_index < n:

        # 定義要返回的值
        result = a
        # 生成新的 a、b值
        a, b = b, a+b
        # 讓當前值+1
        current_index += 1
        print("-----------2222----------")
        yield result

        print("-----------3333------------")
        return "我是return的內容"

    ....省略代碼....

總結

  • 使用了yield關鍵字的函數不再是函數,而是生成器。(使用了yield的函數就是生成器)
  • yield關鍵字有兩點作用:
    • 保存當前運行狀態(斷點),然後暫停執行,即將生成器(函數)掛起
    • 將yield關鍵字後面表達式的值作爲返回值返回,此時可以理解爲起到了return的作用
  • 可以使用next()函數讓生成器從斷點處繼續執行,即喚醒生成器(函數)
  • Python3中的生成器可以使用return返回最終運行的返回值,而Python2中的生成器不允許使用return返回一個返回值(即可以使用return從生成器中退出,但return後不能有任何表達式)。
  • 使用send喚醒

    我們除了可以使用next()函數來喚醒生成器繼續執行外,還可以使用send()函數來喚醒執行。使用send()函數的一個好處是可以在喚醒的同時向斷點處傳入一個附加數據。

    例子:執行到yield時,gen函數作用暫時保存,返回i的值; temp接收下次c.send("python"),send發送過來的值,c.next()等價c.send(None)

    def fibonacci(n):
        # 定義斐波那契數列的前2個值
        a = 0
        b = 1
    
        # 定義當前的位置
        current_index = 0
    
        while current_index < n:
                # 定義要返回的值
                result = a
                # 生成新的 a、b值
                a, b = b, a+b
                # 讓當前值+1
                current_index += 1
    
    
                parms = yield result
                print("send------", parms)
    

    使用send

    # 生成器,生成斐波那契數列
    fib = fibonacci(5)
    # 第一次沒有執行到 return,不用捕獲異常
    # value = next(fib)
    # print(value)
    value = fib.send(None)
    print(value)
    
    value = fib.send("abc")
    print(value)
    
    value = fib.send("def")
    print(value)
    

    運行結果:

    0
    send------ abc
    1
    send------ def
    1
    
    Process finished with exit code 0
    

    注意點:

    使用send啓動生成器的時候傳入的參數必須是None,下次啓動生成器的時候可以加上參數

    提示: 一般第一次啓動生成器使用next

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章