Вопрос

In my code, I need to be able to open and close a device properly, and therefore see the need to use a context manager. While a context manager is usually defined as a class with __enter__ and __exit__ methods, there also seem to be the possibility to decorate a function for use with the context manager (see a recent post and another nice example here).

In the following (working) code snippet, I have implemented the two possibilities; one just need to swap the commented line with the other one:

import time
import contextlib

def device():
    return 42

@contextlib.contextmanager
def wrap():
    print("open")
    yield device
    print("close")
    return

class Wrap(object):
    def __enter__(self):
        print("open")
        return device
    def __exit__(self, type, value, traceback):
        print("close")


#with wrap() as mydevice:
with Wrap() as mydevice:
    while True:
        time.sleep(1)
        print mydevice()

What I try is to run the code and stop it with CTRL-C. When I use the Wrap class in the context manager, the __exit__ method is called as expeced (the text 'close' is printed in the terminal), but when I try the same thing with the wrap function, the text 'close' is not printed to the terminal.

My question: Is there a problem with the code snippet, am I missing something, or why is the line print("close") not called with the decorated function?

Это было полезно?

Решение

The example in the documentation for contextmanager is somewhat misleading. The portion of the function after yield does not really correspond to the __exit__ of the context manager protocol. The key point in the documentation is this:

If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. Thus, you can use a try...except...finally statement to trap the error (if any), or ensure that some cleanup takes place.

So if you want to handle an exception in your contextmanager-decorated function, you need to write your own try that wraps the yield and handle the exceptions yourself, executing cleanup code in a finally (or just block the exception in except and execute your cleanup after the try/except). For example:

@contextlib.contextmanager
def cm():
    print "before"
    exc = None
    try:
        yield
    except Exception, exc:
        print "Exception was caught"
    print "after"
    if exc is not None:
        raise exc

>>> with cm():
...     print "Hi!"
before
Hi!
after

>>> with cm():
...     print "Hi!"
...     1/0
before
Hi!
Exception was caught
after

This page also shows an instructive example.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top