future, yield from そして Task について
先日 は、 Coroutine2Future というものを実装して coroutine の説明をしたが、 実際の coroutine/future/Task についても見てみる。
yield from?
asyncio の croutine の使い方は以下のような感じになる。
import asyncio @asyncio.coroutine def coro(): fut = asyncio.Future() loop = asyncio.get_event_loop() loop.call_later(1, fut.set_result, None) yield from fut return 42 loop = asyncio.get_event_loop() print(loop.run_until_complete(coro()))
さて、ここでよく見るとおかしな気がするのだが、 Future は yield
ではなく yield from
する必要がある。 (yield
だけだと、 Task が RuntimeError
を起こす。) yield from
というのは普通は他の
generator/coroutine に対して使うものだ。 Future を yield from
するとはどういうこと?
答え
Future は Iterable
なので yield from
できます。
Future.__iter__()
の実装は以下のようになっている。
def __iter__(self): if not self.done(): self._blocking = True yield self # This tells Task to wait for completion. assert self.done(), "yield from wasn't used with future" return self.result() # May raise too.
yield from
はこれを実行し、ここから出てきたもの (yield self
の部分) を coroutine 実行側に戻すことになる。
これ意味あるの?
yield self
とはつまり yield from
された future と同じものを返しているということだ。 結局同じものが出てくるのであれば、わざわざ yield from
を強制して __iter__()
を通らせる意味はあるのだろうか?
想像だけれども、 coroutine (generator) と future でどちらも yield from
を使うように統一したかったのかもしれない。
また、動作の面の違いとしては、 coro.send()
で再開するのは上記の yield self
の箇所からになり、 yield from
の返り値としては return self.result()
になるので、 coro.send()
には future
の結果をわざわざ渡す必要がない、というのがある。
実際に、 Task._wakeup()
(出てきた future の callback として使われるメソッド) では future の結果を引き回すようなことはしておらず、 その後の ._step()
での coroutine の再開は常に coro.send(None)
で行うようになっている。