Python關鍵字yield詳解以及Iterable 和Iterator區別

迭代器(Iterator)

爲了理解yield是什麼,首先要明白生成器(generator)是什麼,在講生成器之前先說說迭代器(iterator),當創建一個列表(list)時,你可以逐個的讀取每一項,這就叫做迭代(iteration)。

  1. mylist = [ 1 ,  2 ,  3 ]   
  2.   for  i  in  mylist :   
  3.   print (i)   
  4. 1  
  5. 2  
  6. 3  

Mylist就是一個迭代器,不管是使用複雜的表達式列表,還是直接創建一個列表,都是可迭代的對象。

  1. mylist = [x*x  for  x  in  range( 3 )]   
  2. for  i  in  mylist :   
  3. print (i)   
  4. 0  
  5. 1  
  6. 4  

你可以使用“for··· in ···”來操作可迭代對象,如:list,string,files,這些迭代對象非常方便我們使用,因爲你可以按照你的意願進行重複的讀取。但是你不得不預先存儲所有的元素在內存中,那些對象裏有很多元素時,並不是每一項都對你有用。

生成器(Generators)

生成器同樣是可迭代對象,但是你只能讀取一次,因爲它並沒有把所有值存放內存中,它動態的生成值:

  1. mygenerator = (x*x  for  x  in  range( 3 ))   
  2. for  i  in  mygenerator :   
  3. print (i)   
  4. 0  
  5. 1  
  6. 4  

使用()和[]結果是一樣的,但是,第二次執行“ for in mygenerator”不會有任何結果返回,因爲它只能使用一次。首先計算0,然後計算1,之後計算4,依次類推。

Yield

Yield是關鍵字, 用起來像return,yield在告訴程序,要求函數返回一個生成 器。

  1. def  createGenerator() :   
  2. mylist = range( 3 )   
  3. for  i  in  mylist :   
  4. yield  i*i   
  5.     
  6. mygenerator = createGenerator()  # create a generator   
  7. print (mygenerator)  # mygenerator is an object!   
  8. generator object  createGenerator at  0xb7555c34 >   
  9. for  i  in  mygenerator:   
  10. print (i)   
  11. 0  
  12. 1  
  13. 4  

這個示例本身沒什麼意義,但是它很清晰地說明函數將返回一組僅能讀一次的值,要想掌握yield,首先必須理解的是:當你調用生成器函數的時候,如上例中的createGenerator(),程序並不會執行函數體內的代碼,它僅僅只是返回生成器對象,這種方式頗爲微妙。函數體內的代碼只有直到每次循環迭代(for)生成器的時候纔會運行。

函數第一次運行時,它會從函數開始處直到碰到yield時,就返回循環的第一個值,然後,交互的運行、返回,直到沒有值返回爲止。如果函數在運行但是並沒有遇到yield,就認爲該生成器是空,原因可能是循環終止,或者沒有滿足任何”if/else”。

接下來讀一小段代碼來理解生成器的優點:

控制生成器窮舉

  1. >>>  class  Bank():  # 創建銀行,構造ATM機   
  2. ...    crisis =  False  
  3. ...     def  create_atm( self ) :   
  4. ...         while   not   self .crisis :   
  5. ...             yield   "$100"  
  6. >>> hsbc = Bank()  # 沒有危機時,你想要多少,ATM就可以吐多少   
  7. >>> corner_street_atm = hsbc.create_atm()   
  8. >>>  print (corner_street_atm.next())   
  9. $ 100  
  10. >>>  print (corner_street_atm.next())   
  11. $ 100  
  12. >>>  print ([corner_street_atm.next()  for  cash  in  range( 5 )])   
  13. [ '$100' ,  '$100' ,  '$100' ,  '$100' ,  '$100' ]   
  14. >>> hsbc.crisis =  True   # 危機來臨,銀行沒錢了   
  15. >>>  print (corner_street_atm.next())   
  16. <type  'exceptions.StopIteration' >   
  17. >>> wall_street_atm = hsbc.ceate_atm()  # 新建ATM,銀行仍然沒錢   
  18. >>>  print (wall_street_atm.next())   
  19. <type  'exceptions.StopIteration' >   
  20. >>> hsbc.crisis =  False   # 麻煩就是,即使危機過後銀行還是空的   
  21. >>>  print (corner_street_atm.next())   
  22. <type  'exceptions.StopIteration' >   
  23. >>> brand_new_atm = hsbc.create_atm()  # 構造新的ATM,恢復業務   
  24. >>>  for  cash  in  brand_new_atm :   
  25. ...     print  cash   
  26. $ 100  
  27. $ 100  
  28. $ 100  
  29. $ 100  
  30. $ 100  
  31. $ 100  
  32. $ 100  
  33. $ 100  
  34. $ 100  

對於訪問控制資源,生成器顯得非常有用。

迭代工具,你最好的朋友

迭代工具模塊包含了操做指定的函數用於操作迭代器。 想複製一個迭代器出來?鏈接兩個迭代器?以one liner(這裏的one-liner只需一行代碼能搞定的任務)用內嵌的列表組合一組值?不使用list創建Map/Zip?···,你要做的就是 import itertools,舉個例子吧:

四匹馬賽跑到達終點排名的所有可能性:

  1. >>> horses = [ 1 ,  2 ,  3 ,  4 ]   
  2. >>> races = itertools.permutations(horses)   
  3. >>>  print (races)   
  4. <itertools.permutations object at  0xb754f1dc >   
  5. >>>  print (list(itertools.permutations(horses)))   
  6. [( 1 ,  2 ,  3 ,  4 ),   
  7.  ( 1 ,  2 ,  4 ,  3 ),   
  8.  ( 1 ,  3 ,  2 ,  4 ),   
  9.  ( 1 ,  3 ,  4 ,  2 ),   
  10.  ( 1 ,  4 ,  2 ,  3 ),   
  11.  ( 1 ,  4 ,  3 ,  2 ),   
  12.  ( 2 ,  1 ,  3 ,  4 ),   
  13.  ( 2 ,  1 ,  4 ,  3 ),   
  14.  ( 2 ,  3 ,  1 ,  4 ),   
  15.  ( 2 ,  3 ,  4 ,  1 ),   
  16.  ( 2 ,  4 ,  1 ,  3 ),   
  17.  ( 2 ,  4 ,  3 ,  1 ),   
  18.  ( 3 ,  1 ,  2 ,  4 ),   
  19.  ( 3 ,  1 ,  4 ,  2 ),   
  20.  ( 3 ,  2 ,  1 ,  4 ),   
  21.  ( 3 ,  2 ,  4 ,  1 ),   
  22.  ( 3 ,  4 ,  1 ,  2 ),   
  23.  ( 3 ,  4 ,  2 ,  1 ),   
  24.  ( 4 ,  1 ,  2 ,  3 ),   
  25.  ( 4 ,  1 ,  3 ,  2 ),   
  26.  ( 4 ,  2 ,  1 ,  3 ),   
  27.  ( 4 ,  2 ,  3 ,  1 ),   
  28.  ( 4 ,  3 ,  1 ,  2 ),   
  29.  ( 4 ,  3 ,  2 ,  1 )] 

理解迭代的內部機制:

迭代(iteration)就是對可迭代對象(iterables,實現了__iter__()方法)和迭代器(iterators,實現了__next__()方法)的一個操作過程。可迭代對象是任何可返回一個迭代器的對象,迭代器是應用在迭代對象中迭代的對象,換一種方式說的話就是:iterable對象的__iter__()方法可以返回iterator對象,iterator通過調用next()方法獲取其中的每一個值(譯者注),讀者可以結合Java API中的 Iterable接口和Iterator接口進行類比。

java Iterable接口:

public interface Iterable<T>

Implementing this interface allows an object to be the target of the "foreach" statement.

方法:

Iterator<T> iterator()
Returns an iterator over a set of elements of type T.

 

Returns:
an Iterator.
Iterator接口:
public interface Iterator<E>

An iterator over a collection. Iterator takes the place of Enumeration in the Java collections framework. Iterators differ from enumerations in two ways:

  • Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
  • Method names have been improved.

This interface is a member of the  Java Collections Framework .

boolean hasNext() 
   Returns true if the iteration has more elements. 
E next() 
Returns the next element in the iteration. 
void remove() 
Removes from the underlying collection the last element returned by the iterator (optional operation).

爲什麼一定要去實現Iterable這個接口呢? 爲什麼不直接實現Iterator接口呢?

看一下JDK中的集合類,比如List一族或者Set一族,  
都是實現了Iterable接口,但並不直接實現Iterator接口。  
仔細想一下這麼做是有道理的。 因爲Iterator接口的核心方法next()或者hasNext()  
是依賴於迭代器的當前迭代位置的。  

如果Collection直接實現Iterator接口,勢必導致集合對象中包含當前迭代位置的數據(指針)。  
當集合在不同方法間被傳遞時,由於當前迭代位置不可預置,那麼next()方法的結果會變成不可預知。  
除非再爲Iterator接口添加一個reset()方法,用來重置當前迭代位置。  
但即時這樣,Collection也只能同時存在一個當前迭代位置。  
而Iterable則不然,每次調用都會返回一個從頭開始計數的迭代器。  
多個迭代器是互不干擾的

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章