代码之家  ›  专栏  ›  技术社区  ›  lmiguelvargasf

是否总是可以将实现内容管理器的类转换为使用contextmanager装饰器的函数?

  •  0
  • lmiguelvargasf  · 技术社区  · 6 年前

    我有以下实现上下文管理器协议的类:

    class Indenter:
        def __init__(self):
            self.level = 0
    
        def __enter__(self):
            self.level += 1
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.level -= 1
    
        def print(self, text):
            print('\t' * self.level + text)
    

    with Indenter() as indent:
        indent.print('bye!')
        with indent:
            indent.print('goodbye')
            with indent:
                indent.print('au revoir')
        indent.print('bye bye')
    

    产生以下输出:

        bye!
            goodbye
                au revoir
        bye bye
    

    现在,我想产生相同的功能,但是我不想实现类,而是想使用 contextmanager 装饰工。到目前为止,我有以下代码:

    class Indenter:
        def __init__(self):
            self.level = 0
    
        def print(self, text):
            print('\t' * self.level + text)
    
    
    @contextmanager
    def indenter():
        try:
            i = Indenter()
            i.level += 1
            yield i
        finally:
            i.level -= 1
    

    with indenter() as indent:
        indent.print('hi!')
        with indent:
            indent.print('hello')
            with indent:
                indent.print('bonjour')
        indent.print('hey')
    

    我做错什么了?是否可以实现我对实现上下文管理器的类所做的操作,该类的函数由 上下文管理器

    主要问题:

    是否可以将实现上下文管理器协议的任何类转换为使用 上下文管理器 装潢师?每种选择的局限性是什么?有没有一个比另一个好的例子?

    1 回复  |  直到 6 年前
        1
  •  1
  •   abarnert    6 年前

    你不能做你想做的事,至少不能直接做。

    你的 Indenter.__enter__ 返回 Indenter with indent: 使用那个 压头 对象作为上下文管理器,这很好,因为它是一个。

    indenter 函数产生 压头 缩进: 使用那个 压头 对象作为上下文管理器失败,因为它不是上下文管理器。


    你需要改变一些事情,这样你的回报就不会是 压头 压头

    如果您愿意稍微更改API,可以执行以下操作:

    @contextmanager
    def indenter():
        level=0
        @contextmanager
        def _indenter():
            nonlocal level
            try:
                level += 1
                yield
            finally:
                level -= 1
        def _print(text):
            print('\t' * level + text)
        _indenter.print = _print
        yield _indenter
    

    压头 不创建上下文管理器,但它确实创建了返回上下文管理器的函数。这是内在的 @contextmanager with indenter() as indent: ,不是 with indenter as indent: ,你必须这样做 with indent(): ,不是 with indent .

    level 结束了。然后我们可以 contextmanager 它和钉 print 方法打开。现在:

    >>> with indenter() as indent:
    ...     indent.print('hi!')
    ...     with indent():
    ...         indent.print('hello')
    ...         with indent():
    ...             indent.print('bonjour')
    ...     indent.print('hey')
    hi!
        hello
            bonjour
    hey
    

    如果你想知道为什么我们不能 yield _indenter() (好吧,我们得打电话 _indenter() ,然后在 然后就结果了 yield 但这不是主要问题),问题是 上下文管理器 contextlib 资料来源,你可以看到你怎么写 相反,它会产生一个函数,它将永远产生交替的输入和出口,并给您一个上下文管理器。 next 为每个人 __enter__ __exit__ __进入__ 而不是开着 __init__ 所以它可以 _recreate_cm


    如果你对更多感兴趣,你应该去看看 contextlib2 ,由Nick Coghlan和stdlib的其他作者编写的第三方模块 . 这两个都是用来移植的 上下文库 这是可重用的,但删除了它,因为一个错误,不能工作周围干净。