Tornado @tornado.gen.coroutine 與 yield


在使用 Tornado 的過程中產生了以下疑問:

  • 什么時候需要給函數增加 @tornado.gen.coroutine
  • 什么時候調用函數需要 yield

@tornado.gen.coroutineyield 是如何工作的

包含 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.coroutineyield 必須同時出現調用函數中
  • 如果只是在協程中執行操作或者直接返回結果,有 @tornado.gen.coroutine 和 return(raise Return)就夠了

參考

  1. https://docs.python.org/2.4/ref/yield.html
  2. http://www.tornadoweb.org/en/stable/guide/coroutines.html#how-it-works


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM