1. 程式人生 > >迭代器 生成器, 可迭代物件以及應用場景

迭代器 生成器, 可迭代物件以及應用場景

 

可迭代物件:

實現了迭代器協議的物件就是可迭代物件(實現方式是,實現iter方法)

迭代器

迭代器物件就是實現了iter() 和 next()方法的物件.其中iter()返回迭代器本身,而next()返回容器的下一個元素,在結尾處引發StopInteration異常.

迭代器有兩個方法:
iter() 和 next()

it  = iter(iterable)  # 將一個可迭代物件轉換為迭代器
next(it)  # 獲取下一個迭代器中的下一個值

`注意`
list dic tuple string 並不是迭代器,它們只是可迭代物件.但是可以通過iter(list)的方法
將它們轉換為迭代器.

你可能會問,為什麼list、dict、str等資料型別不是Iterator?

這是因為Python的Iterator物件表示的是一個數據流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,只有在需要返回下一個資料時它才會計算。

Iterator甚至可以表示一個無限大的資料流,例如全體自然數。而使用list是永遠不可能儲存全體自然數的。

for x in [1,2,3,4,5] pass `完全等價於` # 首先獲得Iterator物件: it = iter([1, 2, 3, 4, 5]) # 迴圈: while True: try: # 獲得下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出迴圈 break 

自定義迭代器

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/11/20 20:15' from itertools import islice from collections import Iterator,Iterable # 自定義一個迭代器,求斐波那契序列 class Fib(object): def __init__(self): self.prev = 0 self.curr = 1 def __iter__(self): return self def __next__(self): value = self.curr self.curr += self.prev self.prev = value return value # 迭代器物件 f = Fib() print(isinstance(f,Iterator)) # true L = list(islice(f,0,10)) # islice對可迭代物件進行切片 print(L) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 
class Container:
    def __init__(self, start, end): self.start = start self.end = end def __iter__(self): print("呼叫了 __iter__(self) 方法") return self # 返回迭代器物件本身 def __next__(self): if self.start < self.end: i = self.start self.start = self.start + 1 return i else: raise StopIteration Cont = iter(Container(0, 10)) for i in Cont: print(i) 

總結

  • 所有的iterable都可以通過內建函式iter()轉換為iterator
  • 迭代器的優點:省記憶體.它是一種通過延時建立的方式生成一個序列,只有在需要的時候才被建立.
  • 迭代器物件從集合的第一個元素開始訪問,直到所有的元素被訪問結束,只能往前不能後退
  • 迭代器有兩個基本的方法:iter,text方法
  • 內建函式iter(),next(),本質上都是用的物件的iter()和next()方法.

生成器

Python使用生成器對延遲操作提供了支援.所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果.
這也是生成器的主要好處.

通過列表生成式,我們可以直接建立一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。

所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?這樣就不必建立完整的list,從而節省大量的空間。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器(Generator)。

要建立一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就建立了一個generator

   

建立L和g的區別僅在於最外層的[]和(),L是一個list,而g是一個generator。
我們可以直接打印出list的每一個元素,但我們怎麼打印出generator的每一個元素呢?
如果要一個一個打印出來,可以通過generator的next()方法:

Python有兩種不同的方式提供生成器:

  • 生成器表示式

類似於列表推導式,是用()代替了原來的[].生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表

  • 生成器函式

和常規函式定義一樣,但是返回語句return被yield語句代替了.yield語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次從它離開的地方繼續執行.

  • 使用生成器返回自然數的平方
def gensquares(N):
    for i in range(N): yield i ** 2 for item in gensquares(5): print(item) 
  • 使用普通的函式
# 使用普通的函式

def squares_list(N): ret = [] for i in range(N): ret.append(i * i) return ret for item in squares_list(5): print(item) 

再看生成器

1. 語法上和普通的函式非常相似,都是用def進行定義.唯一的不同是普通的函式是用return返回,而生成器是通過yield語句返回一個值
2.自動實現迭代器協議:對於生成器,Python會自動實現它的可迭代協議,以便用在可以迭代的地方.所以我們可呼叫它的next方法,獲取下一個元素,並且在沒有值可以返回的時候,生成器會自動產生StopIteration異常
3.狀態掛起:生成器使用yield語句返回一值.yield語句掛起該生成器函式的狀態,保留足夠的資訊,以便之後從它離開的地方繼續執行.

示例

首先,生成器的好處是延時計算,一次返回一個結果.也就是所它不會一次返回所有的結果,可以節省記憶體.

sum([i for i in range(1000000000)])
sum((i for i in range(1000000000)))

注意:使用生成器的注意事項

   

 

列表生成器,迭代器和生成器的區別?

列表生成器

現在有個需求,看列表[1,2,3,4,5,6,7,8,9],要求你把列表裡面的每個值都加1,你怎麼實現呢?

方法1: 簡單,迴圈遍歷,然後用另外一個列表來實現

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/12/6 11:26'


# 現在有一個需求,[1,2,3,4,5,6],要求你將裡面的數每個數都加1,你怎麼辦?

# 方法1: 遍歷,然後新增 def func1(num_list): # # 使用append # b = [] # for i in num_list: # b.append(num_list[i]) for index, i in enumerate(num_list): num_list[i] += 1 print(num_list) # 方法2: 使用map,匿名函式.map的作用,就是前面的引數是一個函式,然後是一個可迭代物件 # 將可迭代物件逐個的帶入到前面的函式中,返回一個列表 def func2(num_list): a = map(lambda x:x+1,num_list) print(a) # 方法3: 高階,直接使用列表推導式 def func3(num_list): a = [x+1 for x in num_list] # 列表推導式,前面是一個表示式,表示結果,後面跟一個for加一個可迭代的物件,再後面還可以跟 # 一個if else 語句進行判斷 print(a) if __name__ == '__main__': num_list = [1, 2, 3, 4, 5, 6] func1(num_list) 

生成器

什麼是生成器

通過列表推導式,我們可以直接建立一個列表,但是如果這個列表的長度很大,就很佔用記憶體,比如我們想要建立一個100萬個元素的列表,就會佔用很大的記憶體空間.

如果列表元素可以按照某種演算法推算出來,我們可不可以在一邊迴圈的過程中不斷的推算出後續的元素呢?這樣就不需要一次性分配出一個list所需要的全部的記憶體空間,答案是肯定的,在Python中我們通過生成器的機制,可以實現一邊迴圈一邊分配空間.

生成器是迭代器的一種,Python中有兩種方法來實現生成器,一個是生成器表示式,一個是生成器函式.

生成器表示式:
將列表推導式的[] 換成圓括號就是生成器表示式.

sum([i for i in range(1000000000)])
sum((i for i in range(1000000000)))

生成器函式,帶yield的函式

生成器是一個特殊的函式,可以被用作控制迴圈的迭代行為,python中生成器是迭代器的一種,使用yield代替return返回,每次呼叫yield會暫停,而可以使用next()函式和send()函式恢復生成器.

生成器類似於返回值為一個數組的一個函式,這個函式可以接收引數,可以被呼叫,但是不同於一般的函式會一次性返回包括了所有數值的陣列,生成器一次只能產生一個值,這樣消耗的記憶體數量將大大減小,生成器可以被看作是一個函式,但是它本質上也算是一個迭代器.

小結

  • 凡是可以用作for迴圈的物件都是可迭代物件,都遵循了可迭代協議
  • 凡是可以用next()取出下一個元素,使用iter()返回自身的的迭代器型別
  • 可迭代資料型別如list,dict,str等都是Iteralbe但不是迭代器Iterator,不過可以通過iter(list)函式獲取一個迭代器物件

對於yield的總結

  • 通常的for ... in ..迴圈中,後面的是一個列表或者是字典,或者是字串.它的缺點是很明顯的,就是在迭代的時候一下就分配了全部的記憶體,這樣資料比較大,將會佔用很大的記憶體.

  • 生成器是可以迭代的,但是隻可以讀取它一次,因為用的時候才生成.生成器表示式和列表推導式的區別就是生成器表示式使用小括號而列表推導式使用[]

  • 生成器(generator)能夠迭代的關鍵是他有next()方法,工作原理是通過重複呼叫next()方法,直到捕獲一個StopIteration異常

  • 帶有yield的函式不再是一個普通的函式,而是一個生成器函式.可以用於迭代

  • yield就是return返回的一個值,並且記住這個返回的位置.下一次迭代就從整個位置開始

  • send()和next()的區別就在於send可傳遞引數給yield表示式,這時候傳遞的引數就會作為yield表示式的值,而yield的引數是返回給呼叫者的值,也就是說send可以強行修改上一個yield表示式的值



作者:莫辜負自己的一世韶光
連結:https://www.jianshu.com/p/411352426841
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。