在使用 Tornado 的過程中產生了以下疑問:
- 什么時候需要給函數增加
@tornado.gen.coroutine - 什么時候調用函數需要
yield
@tornado.gen.coroutine 與 yield 是如何工作的
包含 yield 的函數是一個 generator[1]。@gen.coroutine 通過 yield 與 generator 溝通、通過返回 Future 與協程的調用者溝通。
具體溝通情況:
@gen.coroutine收到從 generator 返回的Future- "unwraps"
Future獲得結果 - 將結果發送回 generator 以作為
yield表達式的結果
如何調用協程
上文提到,
@gen.coroutine通過返回Future與協程的調用者溝通
所以我們必須 "unwraps" 這個 Future 才能得到結果。
所以在絕大部分情況下,任何調用協程的函數本身必須是一個協程,並且在調用中要使用 yield。
@gen.coroutine
def good_call():
# yield will unwrap the Future returned by divide() and raise
# the exception.
yield divide(1, 0)
注意,yield 只有在 @gen.coroutine 中才會 "unwraps" 一個 Future,如果沒有 @gen.coroutine,那么 yield 只會將該函數變為一個而普通的生成器,比如下面兩個例子。
- 錯誤的
import tornado.gen
from tornado.ioloop import IOLoop
from tornado.gen import Return
@tornado.gen.coroutine
def call_me():
raise Return('result')
def f():
r = yield call_me()
print(r) # tornado.gen.BadYieldError: yielded unknown object <generator object f at 0x104a34320>
IOLoop.current().run_sync(f)
錯誤的原因是:run_sync 會調用 f(),然后嘗試將 f() 的結果轉換為 Future,轉換的函數如下:
# env/lib/python2.7/site-packages/tornado/gen.py:1259
def convert_yielded(yielded):
"""Convert a yielded object into a `.Future`.
The default implementation accepts lists, dictionaries, and Futures.
If the `~functools.singledispatch` library is available, this function
may be extended to support additional types. For example::
@convert_yielded.register(asyncio.Future)
def _(asyncio_future):
return tornado.platform.asyncio.to_tornado_future(asyncio_future)
.. versionadded:: 4.1
"""
# Lists and dicts containing YieldPoints were handled earlier.
if yielded is None:
return moment
elif isinstance(yielded, (list, dict)):
return multi(yielded)
elif is_future(yielded):
return yielded
elif isawaitable(yielded):
return _wrap_awaitable(yielded)
else:
raise BadYieldError("yielded unknown object %r" % (yielded,))
由於 f() 返回的是一個 generator 對象,不符合轉換的要求,所以報錯。如果給 f() 加上 @tornado.gen.coroutine,那么裝飾器會將 f() 返回的結果轉換為 Future,符合 elif is_future(yielded):,也就能順利運行。
- 正確的
import tornado.gen
from tornado.ioloop import IOLoop
from tornado.gen import Return
@tornado.gen.coroutine
def call_me():
raise Return('result')
@tornado.gen.coroutine
def f():
r = yield call_me()
print(r) # result
IOLoop.current().run_sync(f)
總結
- 當調用一個協程時,
@tornado.gen.coroutine與yield必須同時出現調用函數中 - 如果只是在協程中執行操作或者直接返回結果,有
@tornado.gen.coroutine和 return(raise Return)就夠了
