将上下文管理器与协程一起使用
Using a context manager with a 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
产生未来和未来的错误,我 return 回到屈服点,得到异常,并离开上下文,触发 print('hello out')
.
请注意,如果我只是围绕 yield
做一个 try: finally:
,我确实会打招呼
代码的结构是正确的,上下文管理器和协程这样混合使用是没问题的。 @contextmanager
和 @coroutine
装饰器各自在其装饰函数中为 yield
分配自己的含义,但它们保持独立。
如所写,如果对 http://localhost
的提取成功(或者如果您将其更改为指向工作的服务器),此代码将打印 "hello in" 和 "hello out",但它如果提取引发异常,则不会打印 "hello out"。为此,您需要在装饰器中使用 try/finally
:
@contextmanager
def hello():
print("hello in")
try:
yield
finally:
print("hello out")
此代码中的另一个错误是您 return 从 get()
中获取了一个值。 get()
的return值被忽略;在 Tornado 中要产生输出,你必须调用 self.write()
(或 finish()
或 render()
)。
这是 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
出于这个原因,我建议使用简单的上下文 class 而不是使用 contextlib.contextmanager
,因为使用显式 __enter__
和 __exit__
的 scematics 会更容易:
class hello:
def __enter__(self):
print("hello in")
def __exit__(self,*args):
print("hello out")
这样你就可以 保证 退出代码将 运行 在 with
块的末尾。
此代码无效
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
产生未来和未来的错误,我 return 回到屈服点,得到异常,并离开上下文,触发 print('hello out')
.
请注意,如果我只是围绕 yield
try: finally:
,我确实会打招呼
代码的结构是正确的,上下文管理器和协程这样混合使用是没问题的。 @contextmanager
和 @coroutine
装饰器各自在其装饰函数中为 yield
分配自己的含义,但它们保持独立。
如所写,如果对 http://localhost
的提取成功(或者如果您将其更改为指向工作的服务器),此代码将打印 "hello in" 和 "hello out",但它如果提取引发异常,则不会打印 "hello out"。为此,您需要在装饰器中使用 try/finally
:
@contextmanager
def hello():
print("hello in")
try:
yield
finally:
print("hello out")
此代码中的另一个错误是您 return 从 get()
中获取了一个值。 get()
的return值被忽略;在 Tornado 中要产生输出,你必须调用 self.write()
(或 finish()
或 render()
)。
这是 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
出于这个原因,我建议使用简单的上下文 class 而不是使用 contextlib.contextmanager
,因为使用显式 __enter__
和 __exit__
的 scematics 会更容易:
class hello:
def __enter__(self):
print("hello in")
def __exit__(self,*args):
print("hello out")
这样你就可以 保证 退出代码将 运行 在 with
块的末尾。