[譯] PEP 525--異步生成器

      網(wǎng)友投稿 697 2025-04-02

      PEP原文:https://github.com/python/peps/blob/master/pep-0525.txt


      創(chuàng)建日期:2016-07-18

      簡述

      PEP492引入了對(duì)Python 3.5的原生協(xié)程和async/await句法的支持。本次提案添加了對(duì)異步生成器的支持進(jìn)而來擴(kuò)展Python的異步功能。

      理論和目標(biāo)

      常規(guī)生成器(在PEP 255中引入)的實(shí)現(xiàn),使得編寫復(fù)雜數(shù)據(jù)變得更優(yōu)雅,它們的行為類似于迭代器。

      當(dāng)時(shí)沒有提供async for使用的異步生成器。 編寫異步數(shù)據(jù)生成器變得非常復(fù)雜,因?yàn)楸仨毝x一個(gè)實(shí)現(xiàn)__aiter__和__anext__的方法,才能在async for語句中使用它。

      為了說明異步生成器的重要性,專門做了性能測試,測試結(jié)果表明使用異步生成器要比使用異步迭代器快2倍多。

      下面的代碼是演示了在迭代的過程中等待幾秒

      class?Ticker:

      """Yield?numbers?from?0?to?`to`?every?`delay`?seconds."""

      def?__init__(self,?delay,?to):

      self.delay?=?delay

      self.i?=?0

      self.to?=?to

      def?__aiter__(self):

      return?self

      async?def?__anext__(self):

      i?=?self.i

      if?i?>=?self.to:

      raise?StopAsyncIteration

      self.i?+=?1

      if?i:

      await?asyncio.sleep(self.delay)

      return?i

      我們那可以使用下面的代碼實(shí)現(xiàn)同樣的功能:

      async?def?ticker(delay,?to):

      """Yield?numbers?from?0?to?`to`?every?`delay`?seconds."""

      for?i?in?range(to):

      yield?i

      await?asyncio.sleep(delay)

      詳細(xì)說明

      我們直到在函數(shù)中使用一個(gè)或多個(gè)yield該函數(shù)將變成一個(gè)生成器。

      def?func():????????????#?方法

      return

      def?genfunc():?????????#?生成器方法

      yield

      我們提議使用類似的功能實(shí)現(xiàn)下面異步生成器:

      async?def?coro():??????#?一個(gè)協(xié)程方法

      await?smth()

      async?def?asyncgen():??#?一個(gè)異步生成器方法

      await?smth()

      yield?42

      調(diào)用異步生成器函數(shù)的結(jié)果是異步生成器對(duì)象,它實(shí)現(xiàn)了PEP 492中定義的異步迭代協(xié)議。

      注意:在異步生成器中使用非空return語句會(huì)引發(fā)SyntaxError錯(cuò)誤。

      該協(xié)議需要實(shí)現(xiàn)兩種特殊方法:

      __aiter__方法返回一個(gè)異步迭代器。

      __anext__方法返回一個(gè)awaitable對(duì)象,它使用StopIteration異常來捕獲yield的值,使用StopAsyncIteration異常來表示迭代結(jié)束。

      異步生成器定義了這兩種方法。 讓我們實(shí)現(xiàn)一個(gè)一個(gè)簡單的異步生成器:

      import?asyncio

      async?def?genfunc():

      yield?1

      yield?2

      gen?=?genfunc()

      async?def?start():

      assert?gen.__aiter__()?is?gen

      assert?await?gen.__anext__()?==?1

      assert?await?gen.__anext__()?==?2

      await?gen.__anext__()??#?This?line?will?raise?StopAsyncIteration.

      if?__name__?==?'__main__':

      asyncio.run(start())

      PEP 492提到需要使用事件循環(huán)或調(diào)度程序來運(yùn)行協(xié)程。 因?yàn)楫惒缴善魇窃趨f(xié)程使用的,所以還需要?jiǎng)?chuàng)建一個(gè)事件循環(huán)來運(yùn)行。

      異步生成器可以有try..finally塊,也可以用async with異步上下文管理代碼快。 重要的是提供一種保證,即使在部分迭代時(shí),也可以進(jìn)行垃圾收集,生成器可以安全終止。

      async?def?square_series(con,?to):

      async?with?con.transaction():

      cursor?=?con.cursor(

      'SELECT?generate_series(0,?)?AS?i',?to)

      async?for?row?in?cursor:

      yield?row['i']?**?2

      async?for?i?in?square_series(con,?1000):

      if?i?==?100:

      break

      上面代碼演示了異步生成器在async with中使用,然后使用async for對(duì)異步生成器對(duì)象進(jìn)行迭代處理,同時(shí)我們也可以設(shè)置一個(gè)中斷條件。

      square_series()生成器將被垃圾收集,并沒有異步關(guān)閉生成器的機(jī)制,Python解釋器將無法執(zhí)行任何操作。

      為了解決這個(gè)問題,這里提出以下改進(jìn)建議:

      1.在異步生成器上實(shí)現(xiàn)一個(gè)aclose方法,返回一個(gè)特殊awaittable 對(duì)象。 當(dāng)awaitable拋出GeneratorExit異常的時(shí)候,拋出到掛起的生成器中并對(duì)其進(jìn)行迭代,直到發(fā)生GeneratorExit或StopAsyncIteration。這就是在常規(guī)函數(shù)中使用close方法關(guān)閉對(duì)象一樣,只不過aclose需要一個(gè)事件循環(huán)去執(zhí)行。

      2.不要在異步生成器中使用yield語句,只能用await。

      3.在sys模塊中加兩個(gè)方法:set_asyncgen_hooks() and get_asyncgen_hooks().

      sys.set_asyncgen_hooks()背后的思想是允許事件循環(huán)攔截異步生成器的迭代和終結(jié),這樣最終用戶就不需要關(guān)心終結(jié)問題了,一切正常。

      sys.set_asyncgen_hooks() 可以結(jié)束兩個(gè)參數(shù)

      firstiter:一個(gè)可調(diào)用的,當(dāng)?shù)谝淮蔚惒缴善鲿r(shí)將調(diào)用它。

      finalizer:一個(gè)可調(diào)用的,當(dāng)異步生成器即將被GC時(shí)將被調(diào)用。

      當(dāng)?shù)谝坏惒缴善鲿r(shí),它會(huì)引用到當(dāng)前的finalizer。

      當(dāng)異步生成器即將被垃圾收集時(shí),它會(huì)調(diào)用其緩存的finalizer。假想在事件循環(huán)激活異步生成器開始迭代的時(shí)候,finalizer將調(diào)用一個(gè)aclose()方法.

      例如,以下是如何修改asyncio以允許安全地完成異步生成器:

      #?asyncio/base_events.py

      class?BaseEventLoop:

      def?run_forever(self):

      ...

      old_hooks?=?sys.get_asyncgen_hooks()

      sys.set_asyncgen_hooks(finalizer=self._finalize_asyncgen)

      try:

      ...

      finally:

      sys.set_asyncgen_hooks(*old_hooks)

      ...

      def?_finalize_asyncgen(self,?gen):

      self.create_task(gen.aclose())

      第二個(gè)參數(shù)firstiter,允許事件循環(huán)維護(hù)在其控制下實(shí)例化的弱異步生成器集。這使得可以實(shí)現(xiàn)“shutdown”機(jī)制,來安全地打開的生成器并關(guān)閉事件循環(huán)。

      sys.set_asyncgen_hooks()是特定線程,因此在多個(gè)事件循環(huán)并行的時(shí)候是安全的。

      sys.get_asyncgen_hooks()返回一個(gè)帶有firstiter和finalizer字段的類似于類的結(jié)構(gòu)。

      asyncio

      asyncio事件循環(huán)將使用sys.set_asyncgen_hooks()API來維護(hù)所有被調(diào)度的弱異步生成器,并在生成器被垃圾回收時(shí)侯調(diào)度它們的aclose()方法。

      為了確保asyncio程序可以可靠地完成所有被調(diào)度的異步生成器,我們建議添加一個(gè)新的事件循環(huán)協(xié)程方法loop.shutdown_asyncgens()。 該方法將使用aclose()調(diào)用關(guān)閉所有當(dāng)前打開的異步生成器。

      在調(diào)用loop.shutdown_asyncgens()方法之后,首次迭代新的異步生成器,事件循環(huán)就會(huì)發(fā)出警告。 我們的想法是,在請(qǐng)求關(guān)閉所有異步生成器之后,程序不應(yīng)該執(zhí)行迭代新異步生成器的代碼。

      下面是一個(gè)關(guān)于如何使用Ashutdown_asyncgens的例子:

      try:

      loop.run_forever()

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())#關(guān)閉所有異步迭代器

      loop.close()

      異步生成器對(duì)象

      該對(duì)象以標(biāo)準(zhǔn)Python生成器對(duì)象為模型。 本質(zhì)上異步生成器的行為復(fù)制了同步生成器的行為,唯一的區(qū)別在于API是異步的。

      定義了以下方法和屬性:

      1.agen.__aiter__(): 返回agen.

      2.agen.__anext__(): 返回一個(gè)awaitable對(duì)象, 調(diào)用一次異步生成器的元素。

      3.agen.asend(val): 返回一個(gè)awaitable對(duì)象,它在agen生成器中推送val對(duì)象。 當(dāng)agen還沒迭代時(shí),val必須為None。

      上面的方法類似同步生成器的使用。

      代碼例子:

      import?asyncio

      async?def?gen():

      await?asyncio.sleep(0.1)

      v?=?yield?42

      print(v)

      await?asyncio.sleep(0.2)

      async?def?start():

      g?=?gen()

      await?g.asend(None)??#?Will?return?42?after?sleeping

      #?for?0.1?seconds.

      await?g.asend('hello')??#?Will?print?'hello'?and

      #?raise?StopAsyncIteration

      #?(after?sleeping?for?0.2?seconds.)

      if?__name__?==?'__main__':

      loop?=?asyncio.get_event_loop()

      try:

      loop.run_until_complete(start())

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())

      loop.close()

      4.agen.athrow(typ, [val, [tb]]): 返回一個(gè)awaitable對(duì)象, 這會(huì)向agen生成器拋出一個(gè)異常。

      代碼如下:

      import?asyncio

      async?def?gen():

      try:

      await?asyncio.sleep(0.1)

      yield?'hello'

      except?IndexError:

      await?asyncio.sleep(0.2)

      yield?'world'

      async?def?start():

      g?=?gen()

      v?=?await?g.asend(None)

      print(v)??#?Will?print?'hello'?after

      #?sleeping?for?0.1?seconds.

      v?=?await?g.athrow(IndexError)

      print(v)??#?Will?print?'world'?after

      #?$?sleeping?0.2?seconds.

      if?__name__?==?'__main__':

      loop?=?asyncio.get_event_loop()

      try:

      loop.run_until_complete(start())

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())

      loop.close()

      5.agen.aclose(): 返回一個(gè)awaitable對(duì)象, 調(diào)用該方法會(huì)拋出一個(gè)異常給生成器。

      import?asyncio

      async?def?gen():

      try:

      await?asyncio.sleep(0.1)

      v?=?yield?42

      print(v)

      await?asyncio.sleep(0.2)

      except:

      print("運(yùn)行結(jié)束")

      async?def?start():

      g?=?gen()

      v=await?g.asend(None)

      print(v)

      await?g.aclose()?#不做異常處理會(huì)報(bào)錯(cuò)

      if?__name__?==?'__main__':

      loop?=?asyncio.get_event_loop()

      try:

      loop.run_until_complete(start())

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())

      loop.close()

      6.agen.__name__ and agen.__qualname__:可以返回異步生成器函數(shù)的名字。

      async?def?gen():

      try:

      await?asyncio.sleep(0.1)

      v?=?yield?42

      print(v)

      await?asyncio.sleep(0.2)

      except:

      print("運(yùn)行結(jié)束")

      async?def?start():

      g?=?gen()

      print(g.__aiter__())#輸出async_generator對(duì)象

      print(g.__name__)#輸出gen

      print(g.__qualname__)#輸出gen

      其他的方法

      agen.ag_await:?正等待的對(duì)象(None).?類似當(dāng)前可用的?gi_yieldfrom?for?generators?and?cr_await?for?coroutines.

      agen.ag_frame,?agen.ag_running,?and?agen.ag_code:?同生成器一樣

      StopIteration and StopAsyncIteration 被替換為 RuntimeError,并且不上拋。

      源碼實(shí)現(xiàn)細(xì)節(jié)

      異步生成器對(duì)象(PyAsyncGenObject)與PyGenObject共享結(jié)構(gòu)布局。 除此之外,參考實(shí)現(xiàn)還引入了三個(gè)新對(duì)象:

      PyAsyncGenASend:實(shí)現(xiàn)__anext__和asend()方法的等待對(duì)象。

      PyAsyncGenAThrow:實(shí)現(xiàn)athrow()和aclose()方法的等待對(duì)象。

      PyAsyncGenWrappedValue:來自異步生成器的每個(gè)直接生成的對(duì)象都隱式地裝入此結(jié)構(gòu)中。 這就是生成器實(shí)現(xiàn)如何使用常規(guī)迭代協(xié)議從使用異步迭代協(xié)議生成的對(duì)象中分離出的對(duì)象。 PyAsyncGenASend和PyAsyncGenAThrow是awaitable對(duì)象(它們有__await_方法返回self)類似于coroutine的對(duì)象(實(shí)現(xiàn)__iter__,__ next__,send()和throw()方法)。 本質(zhì)上,它們控制異步生成器的迭代方式

      PyAsyncGenASend and PyAsyncGenAThrow

      PyAsyncGenASend類似生成器對(duì)象驅(qū)動(dòng)__anext__ and asend() 方法,實(shí)裝了異步迭代協(xié)議。

      agen.asend(val) 和agen.__anext__() 返回一個(gè)PyAsyncGenASend對(duì)象的一個(gè)引用。 (它將引用保存回父類agen對(duì)象。)

      數(shù)據(jù)流定義如下:

      1.首次調(diào)用PyAsyncGenASend.send(val)時(shí), val將推入到父類agen對(duì)象 (PyGenObject利用現(xiàn)有對(duì)象。)

      對(duì)PyAsyncGenASend對(duì)象進(jìn)行后續(xù)迭代,將None推送到agen。

      2.首次調(diào)用_PyAsyncGenWrappedValue對(duì)象時(shí),它將被拆箱,并且以未被裝飾的值作為參數(shù)會(huì)引發(fā)StopIteration異常。

      3.異步生成器中的return語句引發(fā)StopAsyncIteration異常,該異常通過PyAsyncGenASend.send()和PyAsyncGenASend.throw()方法傳播。

      4.PyAsyncGenAThrow與PyAsyncGenASend非常相似。 唯一的區(qū)別是PyAsyncGenAThrow.send()在第一次調(diào)用時(shí)會(huì)向父類agen對(duì)象拋出異常(而不是將值推入其中。)

      新的標(biāo)準(zhǔn)庫方法和Types

      1.types.AsyncGeneratorType -- 判斷是否是異步生成器對(duì)象

      2.sys.set_asyncgen_hooks()和 sys.get_asyncgen_hooks()--

      在事件循環(huán)中設(shè)置異步生成器終結(jié)器和迭代-。

      3.inspect.isasyncgen()和 inspect.isasyncgenfunction() :方法內(nèi)省。

      4.asyncio加入新方法:loop.shutdown_asyncgens().

      5.collections.abc.AsyncGenerator:抽象基類的添加。

      是否支持向后兼容

      該提案完全支持向后兼容

      在python3.5,async def里使用yield會(huì)報(bào)錯(cuò),因此在python3.6引入了安全的異步生成器

      性能展示

      def?gen():

      i?=?0

      while?i?

      yield?i

      i?+=?1

      if?__name__?==?'__main__':

      list(gen())

      異步迭代器需求通過__aiter__和__anext__方法自己實(shí)現(xiàn)。

      import?time

      import?asyncio

      N?=?10?**?7

      class?AIter:

      def?__init__(self):

      self.i?=?0

      def?__aiter__(self):

      return?self

      async?def?__anext__(self):

      i?=?self.i

      if?i?>=?N:

      raise?StopAsyncIteration

      self.i?+=?1

      return?i

      async?def?start():

      [_?async?for?_?in?AIter()]

      if?__name__?==?'__main__':

      s=time.time()

      loop=asyncio.get_event_loop()

      try:

      loop.run_until_complete(start())

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())

      loop.close()

      e=time.time()

      print("total?time",e-s)

      輸出

      total?time?5.441649913787842

      import?time

      import?asyncio

      N?=?10?**?7

      async?def?agen():

      for?i?in?range(N):

      yield?i

      async?def?start():

      [_?async?for?_?in?agen()]

      if?__name__?==?'__main__':

      s=time.time()

      loop=asyncio.get_event_loop()

      try:

      loop.run_until_complete(start())

      finally:

      loop.run_until_complete(loop.shutdown_asyncgens())

      loop.close()

      e=time.time()

      print("total?time",e-s)

      輸出

      total?time?2.1055827140808105

      基準(zhǔn)測試表明異步生成器的速度比異步迭代器快了兩倍多。

      設(shè)計(jì)中要注意的事項(xiàng)

      內(nèi)建函數(shù):aiter() and anext()

      最初,PEP 492將__aiter__定義為應(yīng)返回等待對(duì)象的方法,從而產(chǎn)生異步迭代器。

      但是,在CPython 3.5.2中,重新定義了__aiter__可以直接返回異步迭代器。

      為了避免破壞向后兼容性,決定Python 3.6將支持兩種方式:__aiter__仍然可以在發(fā)出DeprecationWarning時(shí)返回等待狀態(tài)。由于Python 3.6中__aiter__的這種雙重性質(zhì),我們無法添加內(nèi)置的aiter()的同步實(shí)現(xiàn)。 因此,建議等到Python 3.7。

      將放在單獨(dú)的pep中也就是后來的pep530.

      對(duì)于異步生成器,yield from也不那么重要,因?yàn)椴恍枰峁┰趨f(xié)程之上實(shí)現(xiàn)另一個(gè)協(xié)同程序協(xié)議的機(jī)制。為了組合異步生成器,可以使用async for簡化這個(gè)過程:

      async?def?g1():

      yield?1

      yield?2

      async?def?g2():

      async?for?v?in?g1():

      yield?v

      它們可以使用異步生成器實(shí)現(xiàn)類似于contextlib.contextmanager的概念。 例如,可以實(shí)現(xiàn)以下模式:

      [譯] PEP 525--異步生成器

      @async_context_manager

      async?def?ctx():

      await?open()

      try:

      yield

      finally:

      await?close()

      async?with?ctx():

      await?...

      另一個(gè)原因是從__anext__對(duì)象返回的對(duì)象來推送數(shù)據(jù)并將異常拋出到異步生成器中,很難正確地執(zhí)行此操作。 添加顯式的asend()和athrow()更獲取異常后的數(shù)據(jù)。

      在實(shí)現(xiàn)方面,asend()是__anext__更通用的版本,而athrow()與aclose()非常相似。 因此,為異步生成器定義這些方法不會(huì)增加任何額外的復(fù)雜性

      代碼示例

      async?def?ticker(delay,?to):

      for?i?in?range(to):

      yield?i

      await?asyncio.sleep(delay)

      async?def?run():

      async?for?i?in?ticker(1,?10):

      print(i)

      import?asyncio

      loop?=?asyncio.get_event_loop()

      try:

      loop.run_until_complete(run())

      finally:

      loop.close()

      這代碼將打出0-9,每個(gè)數(shù)字之間的間隔為1s。

      提議者

      Guido, 2016年9月6日

      參考資料

      [1]????https://github.com/1st1/cpython/tree/async_gen

      [2]????https://mail.python.org/pipermail/python-dev/2016-September/146267.html

      [3]????http://bugs.python.org/issue28003

      本文章翻譯整理自,pep525

      Source: https://github.com/python/peps/blob/master/pep-0525.txt

      翻譯能力有限還請(qǐng)大家多多指教。

      華為云APP python

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:數(shù)據(jù)管理平臺(tái)分析對(duì)比,零代碼數(shù)據(jù)協(xié)作平臺(tái)
      下一篇:如何固定excel表頭打印的教程
      相關(guān)文章
      国产成人亚洲综合一区| 亚洲神级电影国语版| 亚洲一区二区三区高清视频| 亚洲国产成人私人影院| 亚洲一区二区三区自拍公司| 久久亚洲AV永久无码精品| 亚洲午夜激情视频| 国产日产亚洲系列最新| 区久久AAA片69亚洲| 国产亚洲色视频在线| 久久亚洲av无码精品浪潮| 久久亚洲av无码精品浪潮| 在线日韩日本国产亚洲| 亚洲宅男天堂在线观看无病毒| 久久精品国产亚洲5555| 久久精品国产精品亚洲艾草网美妙 | 色九月亚洲综合网| 国产亚洲精品欧洲在线观看| 国产在亚洲线视频观看| mm1313亚洲精品国产| 国产精品亚洲不卡一区二区三区| 亚洲麻豆精品国偷自产在线91| 亚洲av高清在线观看一区二区| 午夜亚洲福利在线老司机| 亚洲午夜激情视频| 国产精品久久久亚洲| 亚洲日本va午夜中文字幕一区| 亚洲AV无码久久精品成人| 中文字幕不卡亚洲| 亚洲精品你懂的在线观看| 亚洲国产精品无码久久久蜜芽| 亚洲日本一区二区| 亚洲免费闲人蜜桃| 中文字幕精品三区无码亚洲| 午夜亚洲国产理论片二级港台二级 | 亚洲精品无码不卡| 亚洲精品美女久久久久| 亚洲精品国产日韩| 亚洲?V乱码久久精品蜜桃| 国产亚洲精久久久久久无码77777| 久久精品亚洲综合|