How can I mix decorators with the @contextmanager decorator?
Here is the code I'm working with:
from contextlib import contextmanager
from functools import wraps
class with_report_status(object):
def __init__(self, message):
self.message = message
def __call__(self, f):
@wraps(f)
def wrapper(_self, *a, **kw):
try:
return f(_self, *a, **kw)
except:
log.exception("Handling exception in reporting operation")
if not (hasattr(_self, 'report_status') and _self.report_status):
_self.report_status = self.message
raise
return wrapper
class MyClass(object):
@contextmanager
@with_report_status('unable to create export workspace')
def make_workspace(self):
temp_dir = tempfile.mkdtemp()
log.debug("Creating working directory in %s", temp_dir)
开发者_JS百科 self.workspace = temp_dir
yield self.workspace
log.debug("Cleaning up working directory in %s", temp_dir)
shutil.rmtree(temp_dir)
@with_report_status('working on step 1')
def step_one(self):
# do something that isn't a context manager
The problem is, @with_report_status
does not yield, as expected by @contextmanager
. However, I can't wrap it the other way around either, because @contextmanager
returns a generator object (i think!) instead of the value itself.
How can I make @contextmanager
play nice with decorators?
Try moving @contextmanager at the bottom of the decorator list.
That is kind of a weird question: @contextmanager
returns a context manager, not a generator. But for some reason you want to treat that context manager like a function? That's not something you can make work, they have nothing in common.
I think what you want is a MyClass.make_workspace
that is context manager and also has a report_status
field in case of exceptions. For that you need to write a context manager yourself that sets this field in it's __exit__
method, @contextmanager
can't help you here.
You can subclass contextlib.GeneratorContextManager
to avoid most of the work. It's not documented, so use the source, Luke.
精彩评论