迭代器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會後退。
可迭代對象
我們已經知道可以對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