在使用 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)就夠了