2016-06-20 24 views
3

Dieser Code funktioniert nichteinen Kontext-Manager mit einem Koroutine Mit

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()             

Und der Grund, warum es nicht funktioniert ist, dass der Kontext-Manager Nachgeben und die Koroutine nachgebend sind in ihrem Verhalten unvereinbar.

Bestätigen Sie, dass der einzige Weg, dies zu erreichen ist, eine try finally verwenden (besonders ärgerlich, wenn der Context-Manager-Code an vielen Stellen verwendet werden muss). Vielleicht gibt es einen subtilen Trick, von dem ich nichts weiß? Googeln hat nicht geholfen.

bearbeiten

Dies ist die Ausgabe I

(venv) [email protected]: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 

Der Punkt ist, dass ich nie die hello out Meldung. Ich würde erwarten, dass, sobald fetch die Zukunft und die zukünftigen Fehler hervorbringt, ich zurück zu der Yield-Point, erhalten Sie die Ausnahme, und verlassen Sie den Kontext, Triggerung der print('hello out').

Bitte beachte, dass ich tun hallo, wenn ich nur ein try: finally: um das zu tun yield

+0

Vermutlich liegt das daran, dass Sie etwas mit "__enter__" und "__exit__" nicht "__aenter__" und "__aexit__" machen. – jonrsharpe

+0

Ich verwende keine Async. Ich bin immer noch auf Python 3.4 mit Vanille Tornado. @jonrsharpe –

Antwort

4

Die Struktur des Codes ist richtig, und es ist in Ordnung Kontext-Manager und Koroutinen diese Art und Weise zu mischen. Die Dekoratoren @contextmanager und @coroutine ordnen ihre eigenen Bedeutungen innerhalb ihrer dekorierten Funktionen yield zu, aber sie bleiben unabhängig.

Wie geschrieben, wird dieser Code "hallo in" und "hallo out" drucken, wenn der Abruf zu http://localhost erfolgreich ist (oder wenn Sie es ändern, um auf einen Server zu verweisen, der funktioniert), aber es wird nicht gedruckt "hallo out "Wenn der Abruf eine Ausnahme auslöst. Um das zu tun, müssen Sie ein try/finally in Ihrem Dekorateur verwenden:

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

Ein anderer Fehler in diesem Code ist, dass Sie einen Wert von get() sind zurück. Der Rückgabewert get() wird ignoriert. in Tornado, um die Ausgabe zu produzieren, müssen Sie self.write() (oder finish() oder render()) aufrufen.

+0

Richtig für die Beobachtung. Wie auch immer, der Punkt ist, dass ich nie "hallo out" sehe. –

+0

Der Beitrag wurde aus Gründen der Klarheit geändert. –

+0

Nebenbei bemerkt bin ich überrascht, dass es nicht so ist. Ich sehe keinen unmittelbaren Grund, warum es nicht sollte, und ich stimme Ihrer Beobachtung zu. Aber ich kann es aus irgendeinem Grund nicht schaffen, und ich kann nicht verstehen warum. –

1

Dies ist ein Mechaniker von contextlib.contextmanager, wenn eine Ausnahme innerhalb des with Block angehoben wird, dass Fehler in hello geworfen wird, so dass es den Zugriff auf die Ausnahmedetails und ist eine Änderung gegeben, es zu unterdrücken (wie ein echter Kontext-Manager) zum Beispiel:

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) 

die Ausgabe dieses Codebeispiel ist:

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 

aus diesem Grund würde ich da die Verwendung einer einfachen Kontextklasse anstelle der Verwendung contextlib.contextmanager empfehlen scematics wird mit expliziten __enter__ und __exit__ einfacher sein:

class hello: 
    def __enter__(self): 
     print("hello in") 
    def __exit__(self,*args): 
     print("hello out") 

Auf diese Weise können , die am Ende des with Block läuft der Exit-Code garantiert sind.