使用带协程的上下文管理器

时间:2016-06-20 08:54:02

标签: python asynchronous tornado coroutine

此代码不起作用

from contextlib import contextmanager                                                     
import tornado.ioloop                                                                     
import tornado.web                                                                        
from tornado import gen                                                                   
from tornado.httpclient import AsyncHTTPClient                                            


@contextmanager                                                                           
def hello():                                                                              
    print("hello in")                                                                     
    yield                                                                                 
    print("hello out")                                                                    


class MainHandler(tornado.web.RequestHandler):                                            
    @gen.coroutine                                                                        
    def get(self):                                                                        
        client = AsyncHTTPClient()                                                        
        with hello():                                                                     
            result = yield client.fetch("http://localhost")                               
        return "Hello "+str(result)                                                       

app = tornado.web.Application([('/', MainHandler)])                                       
app.listen(12345)                                                                         
tornado.ioloop.IOLoop.current().start()                                                   

它不起作用的原因是上下文管理器屈服和协程屈服在行为上是不相容的。

您是否确认实现此目的的唯一方法是使用try finally(如果必须在许多地方使用上下文管理器代码,则特别烦人)。也许有一个我不知道的微妙技巧?谷歌搜索没有帮助。

修改

这是我得到的输出

(venv) sborini@Mac-34363bd19f52:tornado$ python test.py 
hello in
ERROR:tornado.application:Uncaught exception GET / (::1)
HTTPServerRequest(protocol='http', host='localhost:12345', method='GET', uri='/', version='HTTP/1.1', remote_ip='::1', headers={'Upgrade-Insecure-Requests': '1', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36', 'Accept-Language': 'en-US,en;q=0.8,it;q=0.6', 'Connection': 'keep-alive', 'Host': 'localhost:12345', 'Accept-Encoding': 'gzip, deflate, sdch'})
Traceback (most recent call last):
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/web.py", line 1445, in _execute
    result = yield result
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/gen.py", line 1008, in run
    value = future.result()
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/concurrent.py", line 232, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 3, in raise_exc_info
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/gen.py", line 1014, in run
    yielded = self.gen.throw(*exc_info)
  File "test.py", line 20, in get
    result = yield client.fetch("http://localhost")
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/gen.py", line 1008, in run
    value = future.result()
  File "/Users/sborini/Work/Experiments/tornado/venv/lib/python3.4/site-packages/tornado/concurrent.py", line 232, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 3, in raise_exc_info
ConnectionRefusedError: [Errno 61] Connection refused
ERROR:tornado.access:500 GET / (::1) 5.04ms

重点是我从未收到hello out消息。我希望,一旦fetch产生未来和未来的错误,我将返回到屈服点,获得异常,并离开上下文,触发print('hello out')

请注意,如果我只是在try: finally:附近yield

,我确实会打招呼

2 个答案:

答案 0 :(得分:4)

代码的结构是正确的,以这种方式混合上下文管理器和协同程序是很好的。 @contextmanager@coroutine装饰器各自在其装饰函数中为yield指定了自己的含义,但它们保持独立。

如上所述,此代码将打印&#34;你好&#34;并且&#34;你好&#34;如果获取http://localhost成功(或者如果你将其更改为指向有效的服务器),但它不会打印&#34;你好&#34;如果fetch引发异常。为此,您需要在装饰器中使用try/finally

@contextmanager
def hello():                                                                              
    print("hello in")                                                                     
    try:
        yield                                                                                 
    finally:
        print("hello out")   

此代码中的另一个错误是您从get()返回值。忽略get()的返回值;在龙卷风中产生输出,您必须致电self.write()(或finish()render())。

答案 1 :(得分:1)

这是istream_iterator的机制,当在std::ifstream ifs(file_name); std::istream_iterator<unsigned char> start(ifs), end; std::vector<unsigned char> raw_data(start, end); 块内引发异常时会将错误引入contextlib.contextmanager,以便它可以访问异常详细信息并获得更改以禁止它(例如真正的上下文管理器),例如:

with

此代码示例的输出为:

hello

出于这个原因,我建议使用简单的上下文类而不是from contextlib import contextmanager @contextmanager def hello(): print("hello in") try: yield except: print("an exception was thrown into the generator! exit code would not have been run!") raise #commenting this out would suppress the original error which __exit__ can do by returning True finally: print("hello out") def get(): with hello(): result = yield "VALUE" return "Hello "+str(result) gen = get() next(gen) gen.throw(TypeError) ,因为使用明确的hello in an exception was thrown into the generator! exit code would not have been run! hello out Traceback (most recent call last): File "/Users/Tadhg/Documents/codes/test.py", line 24, in <module> gen.throw(TypeError) File "/Users/Tadhg/Documents/codes/test.py", line 19, in get result = yield "VALUE" TypeError contextlib.contextmanager可以更轻松地使用scematics:

__enter__

这样,您保证退出代码将在__exit__块的末尾运行。