Корутины торнадо не работают в Ситоне


Этот код работает в Python 3.4.3 с использованием Tornado 4.1-он спит в течение 1 секунды, а затем печатает "Hello World! 123". Но при компиляции с использованием Cython (я пробовал версии 0.20. 1post0 и 0.23 dev), он ничего не делает.

import tornado.ioloop
import datetime
from tornado import gen

@gen.coroutine
def test():
    yield gen.Task(ioloop.add_timeout, datetime.timedelta(seconds=1))
    return 123

@gen.coroutine
def hello_world():
    print('Hello World! {}'.format((yield test())))

ioloop = tornado.ioloop.IOLoop().instance()
ioloop.run_sync(hello_world)

Команды, которые я использовал для сборки и запуска версии Cython:

cython --embed -o hello.c hello.py
gcc -shared -fPIC -O0 -Wall -I/usr/include/python3.4 -o hello.so hello.c
python -c 'import hello'
1 2

1 ответ:

Обновление: сопрограммы Cython поддерживаются изначально, начиная с Tornado 4.3. Описанный ниже способ решения проблемы применим только к старым версиям Tornado.


Корутины Торнадо в настоящее время не поддерживаются с помощью Cython. Основная проблема заключается в том, что генератор, скомпилированный Cython, не передает isinstance(types.GeneratorType) (и в прошлый раз, когда я смотрел, не было другого класса, который можно было бы использовать вместо него).

Лучшим решением для этого было бы изменение в Cython, чтобы добавить общий базовый класс для генераторов, но в качестве быстрого hack i've had some success with this patch to tornado/gen.py:

diff --git a/tornado/gen.py b/tornado/gen.py
index aa931b4..b348f21 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -91,6 +91,12 @@ from tornado.concurrent import Future, TracebackFuture
 from tornado.ioloop import IOLoop
 from tornado.stack_context import ExceptionStackContext, wrap

+def _is_generator(obj):
+    # cython generates a new generator type for each module without a
+    # common base class :(
+    return (isinstance(obj, types.GeneratorType) or
+            str(type(obj)) == "<type 'generator'>")
+

 class KeyReuseError(Exception):
     pass
@@ -147,7 +153,7 @@ def engine(func):
             except (Return, StopIteration) as e:
                 result = getattr(e, 'value', None)
             else:
-                if isinstance(result, types.GeneratorType):
+                if _is_generator(result):
                     def final_callback(value):
                         if value is not None:
                             raise ReturnValueIgnoredError(
@@ -219,7 +225,7 @@ def coroutine(func):
                 future.set_exc_info(sys.exc_info())
                 return future
             else:
-                if isinstance(result, types.GeneratorType):
+                if _is_generator(result):
                     def final_callback(value):
                         deactivate()
                         future.set_result(value)