国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

Python中的迭代器與生成器高級用法解析

2019-11-25 16:41:12
字體:
來源:轉載
供稿:網友

迭代器

迭代器是依附于迭代協議的對象――基本意味它有一個next方法(method),當調用時,返回序列中的下一個項目。當無項目可返回時,引發(raise)StopIteration異常。

迭代對象允許一次循環。它保留單次迭代的狀態(位置),或從另一個角度講,每次循環序列都需要一個迭代對象。這意味我們可以同時迭代同一個序列不只一次。將迭代邏輯和序列分離使我們有更多的迭代方式。

調用一個容器(container)的__iter__方法創建迭代對象是掌握迭代器最直接的方式。iter函數為我們節約一些按鍵。

>>> nums = [1,2,3]   # note that ... varies: these are different objects>>> iter(nums)              <listiterator object at ...>>>> nums.__iter__()           <listiterator object at ...>>>> nums.__reversed__()         <listreverseiterator object at ...>>>> it = iter(nums)>>> next(it)      # next(obj) simply calls obj.next()1>>> it.next()2>>> next(it)3>>> next(it)Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration

當在循環中使用時,StopIteration被接受并停止循環。但通過顯式引發(invocation),我們看到一旦迭代器元素被耗盡,存取它將引發異常。

使用for...in循環也使用__iter__方法。這允許我們透明地開始對一個序列迭代。但是如果我們已經有一個迭代器,我們想在for循環中能同樣地使用它們。為了實現這點,迭代器除了next還有一個方法__iter__來返回迭代器自身(self)。

Python中對迭代器的支持無處不在:標準庫中的所有序列和無序容器都支持。這個概念也被拓展到其它東西:例如file對象支持行的迭代。

>>> f = open('/etc/fstab')>>> f is f.__iter__()True

file自身就是迭代器,它的__iter__方法并不創建一個單獨的對象:僅僅單線程的順序讀取被允許。

生成表達式
第二種創建迭代對象的方式是通過 生成表達式(generator expression) ,列表推導(list comprehension)的基礎。為了增加清晰度,生成表達式總是封裝在括號或表達式中。如果使用圓括號,則創建了一個生成迭代器(generator iterator)。如果是方括號,這一過程被‘短路'我們獲得一個列表list。

>>> (i for i in nums)          <generator object <genexpr> at 0x...>>>> [i for i in nums][1, 2, 3]>>> list(i for i in nums)[1, 2, 3]

在Python 2.7和 3.x中列表表達式語法被擴展到 字典和集合表達式。一個集合set當生成表達式是被大括號封裝時被創建。一個字典dict在表達式包含key:value形式的鍵值對時被創建:

>>> {i for i in range(3)}  set([0, 1, 2])>>> {i:i**2 for i in range(3)}  {0: 0, 1: 1, 2: 4}

如果您不幸身陷古老的Python版本中,這個語法有點糟:

>>> set(i for i in 'abc')set(['a', 'c', 'b'])>>> dict((i, ord(i)) for i in 'abc'){'a': 97, 'c': 99, 'b': 98}

生成表達式相當簡單,不用多說。只有一個陷阱值得提及:在版本小于3的Python中索引變量(i)會泄漏。

生成器

生成器是產生一列結果而不是單一值的函數。

第三種創建迭代對象的方式是調用生成器函數。一個 生成器(generator) 是包含關鍵字yield的函數。值得注意,僅僅是這個關鍵字的出現完全改變了函數的本質:yield語句不必引發(invoke),甚至不必可接觸。但讓函數變成了生成器。當一個函數被調用時,其中的指令被執行。而當一個生成器被調用時,執行在其中第一條指令之前停止。生成器的調用創建依附于迭代協議的生成器對象。就像常規函數一樣,允許并發和遞歸調用。
當next被調用時,函數執行到第一個yield。每次遇到yield語句獲得一個作為next返回的值,在yield語句執行后,函數的執行又被停止。

>>> def f():...  yield 1...  yield 2>>> f()                  <generator object f at 0x...>>>> gen = f()>>> gen.next()1>>> gen.next()2>>> gen.next()Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration

讓我們遍歷單個生成器函數調用的整個歷程。

>>> def f():...  print("-- start --")...  yield 3...  print("-- middle --")...  yield 4...  print("-- finished --")>>> gen = f()>>> next(gen)-- start --3>>> next(gen)-- middle --4>>> next(gen)              -- finished --Traceback (most recent call last): ...StopIteration

相比常規函數中執行f()立即讓print執行,gen不執行任何函數體中語句就被賦值。只有當gen.next()被next調用,直到第一個yield部分的語句才被執行。第二個語句打印-- middle --并在遇到第二個yield時停止執行。第三個next打印-- finished --并且到函數末尾,因為沒有yield,引發了異常。

當函數yield之后控制返回給調用者后發生了什么?每個生成器的狀態被存儲在生成器對象中。從這點看生成器函數,好像它是運行在單獨的線程,但這僅僅是假象:執行是嚴格單線程的,但解釋器保留和存儲在下一個值請求之間的狀態。

為何生成器有用?正如關于迭代器這部分強調的,生成器函數只是創建迭代對象的又一種方式。一切能被yield語句完成的東西也能被next方法完成。然而,使用函數讓解釋器魔力般地創建迭代器有優勢。一個函數可以比需要next和__iter__方法的類定義短很多。更重要的是,相比不得不對迭代對象在連續next調用之間傳遞的實例(instance)屬性來說,生成器的作者能更簡單的理解局限在局部變量中的語句。

還有問題是為何迭代器有用?當一個迭代器用來驅動循環,循環變得簡單。迭代器代碼初始化狀態,決定是否循環結束,并且找到下一個被提取到不同地方的值。這凸顯了循環體――最值得關注的部分。除此之外,可以在其它地方重用迭代器代碼。

雙向通信
每個yield語句將一個值傳遞給調用者。這就是為何PEP 255引入生成器(在Python2.2中實現)。但是相反方向的通信也很有用。一個明顯的方式是一些外部(extern)語句,或者全局變量或共享可變對象。通過將先前無聊的yield語句變成表達式,直接通信因PEP 342成為現實(在2.5中實現)。當生成器在yield語句之后恢復執行時,調用者可以對生成器對象調用一個方法,或者傳遞一個值 給 生成器,然后通過yield語句返回,或者通過一個不同的方法向生成器注入異常。

第一個新方法是send(value),類似于next(),但是將value傳遞進作為yield表達式值的生成器中。事實上,g.next()和g.send(None)是等效的。

第二個新方法是throw(type, value=None, traceback=None),等效于在yield語句處

raise type, value, traceback

不像raise(從執行點立即引發異常),throw()首先恢復生成器,然后僅僅引發異常。選用單次throw就是因為它意味著把異常放到其它位置,并且在其它語言中與異常有關。

當生成器中的異常被引發時發生什么?它可以或者顯式引發,當執行某些語句時可以通過throw()方法注入到yield語句中。任一情況中,異常都以標準方式傳播:它可以被except和finally捕獲,或者造成生成器的中止并傳遞給調用者。

因完整性緣故,值得提及生成器迭代器也有close()方法,該方法被用來讓本可以提供更多值的生成器立即中止。它用生成器的__del__方法銷毀保留生成器狀態的對象。

讓我們定義一個只打印出通過send和throw方法所傳遞東西的生成器。

>>> import itertools>>> def g():...   print '--start--'...   for i in itertools.count():...     print '--yielding %i--' % i...     try:...       ans = yield i...     except GeneratorExit:...       print '--closing--'...       raise...     except Exception as e:...       print '--yield raised %r--' % e...     else:...       print '--yield returned %s--' % ans>>> it = g()>>> next(it)--start----yielding 0--0>>> it.send(11)--yield returned 11----yielding 1--1>>> it.throw(IndexError)--yield raised IndexError()----yielding 2--2>>> it.close()--closing--

注意: next還是__next__?

在Python 2.x中,接受下一個值的迭代器方法是next,它通過全局函數next顯式調用,意即它應該調用__next__。就像全局函數iter調用__iter__。這種不一致在Python 3.x中被修復,it.next變成了it.__next__。對于其它生成器方法――send和throw情況更加復雜,因為它們不被解釋器隱式調用。然而,有建議語法擴展讓continue帶一個將被傳遞給循環迭代器中send的參數。如果這個擴展被接受,可能gen.send會變成gen.__send__。最后一個生成器方法close顯然被不正確的命名了,因為它已經被隱式調用。

鏈式生成器
注意: 這是PEP 380的預覽(還未被實現,但已經被Python3.3接受)

比如說我們正寫一個生成器,我們想要yield一個第二個生成器――一個子生成器(subgenerator)――生成的數。如果僅考慮產生(yield)的值,通過循環可以不費力的完成:

subgen = some_other_generator()for v in subgen:  yield v

然而,如果子生成器需要調用send()、throw()和close()和調用者適當交互的情況下,事情就復雜了。yield語句不得不通過類似于前一章節部分定義的try...except...finally結構來保證“調試”生成器函數。這種代碼在PEP 380中提供,現在足夠拿出將在Python 3.3中引入的新語法了:

yield from some_other_generator()

像上面的顯式循環調用一樣,重復從some_other_generator中產生值直到沒有值可以產生,但是仍然向子生成器轉發send、throw和close。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 永登县| 邹平县| 邛崃市| 湘潭县| 岑溪市| 台东市| 水城县| 河曲县| 仙桃市| 拉孜县| 苗栗市| 宣武区| 永年县| 鄂尔多斯市| 古蔺县| 江达县| 高雄市| 格尔木市| 慈利县| 岳阳县| 安溪县| 神池县| 锦屏县| 林甸县| 盐池县| 诸暨市| 青河县| 宜宾县| 新绛县| 大埔县| 辉南县| 安宁市| 密山市| 临朐县| 新泰市| 涟源市| 贺州市| 邓州市| 江门市| 濮阳县| 新干县|