如何将装饰器与@contextmanager装饰器混合使用?

7 投票
2 回答
4496 浏览
提问于 2025-04-16 10:13

这是我正在使用的代码:

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

问题是,@with_report_status 没有像 @contextmanager 预期的那样产生结果。不过,我也不能反过来包裹它,因为 @contextmanager 返回的是一个生成器对象(我想是这样!),而不是直接的值。

我该如何让 @contextmanager 和装饰器配合得更好呢?

2 个回答

0

这个问题有点奇怪:@contextmanager 返回的是一个上下文管理器,而不是生成器。但你似乎想把这个上下文管理器当成一个函数来用?这其实是行不通的,因为它们之间没有什么共同点。

我觉得你想要的是一个 MyClass.make_workspace,它既是上下文管理器,又有一个 report_status 字段,用来处理异常情况。为了实现这个,你需要自己写一个上下文管理器,在它的 __exit__ 方法里设置这个字段,@contextmanager 在这里帮不了你。

你可以通过继承 contextlib.GeneratorContextManager 来省去大部分工作。这个内容没有文档说明,所以你得查看源代码,Luke。

5

试着把 @contextmanager 放到装饰器列表的最下面。

撰写回答