Python 是否始终可以将实现content manager的类转换为使用contextmanager装饰器的函数?

Python 是否始终可以将实现content manager的类转换为使用contextmanager装饰器的函数?,python,contextmanager,Python,Contextmanager,我有以下实现上下文管理器协议的类: 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, te

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

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
decorator。到目前为止,我有以下代码:

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')
我做错了什么?有没有可能实现我在使用由
contextmanager
decorator修饰的函数实现上下文管理器的类所做的事情

主要问题:


是否可以将实现上下文管理器协议的任何类转换为使用
contextmanager
decorator的函数?每个选项的限制是什么?有没有一种情况比另一种更好?

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

您的
压头。\uuu输入\uu
返回一个
压头
对象。然后,带有缩进的嵌套
使用该
Indenter
对象作为上下文管理器,这很好,因为它是一个上下文管理器

您的
压头
函数将生成一个
压头
对象。然后,使用indent:嵌套的
将该
Indenter
对象用作上下文管理器,但失败了,因为它不是上下文管理器


您需要更改内容,以便返回的不是
Indenter
对象,而是对
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
现在,
indenter
不创建上下文管理器,但它创建了一个返回上下文管理器的函数。这是
@contextmanager
装饰器的固有功能,就像您必须使用压头()作为压头:
,而不是使用压头作为压头:,您必须使用压头()执行
,而不是使用压头

否则,一切都很简单。我没有使用递归,而是创建了一个新函数,在闭包中存储
level
。然后我们可以
contextmanager
it并将
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\u indenter()
(好吧,我们必须调用
\u indenter()
,然后将
print
添加到结果上,然后
yield
,但这不是主要问题),问题是
contextmanager
需要一个生成一次的生成器函数,并在每次调用时为您提供一个单一用途的上下文管理器。如果您阅读源代码,您可以看到如何编写类似于
contextmanager
的东西,它采用一个函数,生成交替的进入和退出,并为您提供一个上下文管理器,该管理器为每个
\uuuuuuuuuuu进入和退出执行
\uuuuuuu下一步
。或者,您可以编写一个类,在
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>而不是
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
上创建生成器,这样它就可以像用作装饰器而不是上下文管理器时那样正确地执行
\uucm
操作。但在这一点上,为了避免编写一个类,您编写了两个类和一个装饰器,这似乎有点愚蠢



如果您对更多内容感兴趣,您应该查看由Nick Coghlan和stdlib
contextlib
的其他作者编写的第三方模块。它既可用于将
contextlib
特性向后移植到较旧版本的Python,也可用于试验Python未来版本的新特性。IIRC,在某一点上,他们有一个可重用的
@contextmanager
版本,但由于一个无法干净解决的bug,他们将其删除了。

为什么?你已经有了工作代码,现在你想把它包装成一个包装成
contextmanager
的函数,而你已经有了一个实现上下文管理器协议的类…@ForceBru,这只是为了实验。老实说,我没有实际的理由,只是好奇是否有可能做到。@abarnet,我想实现一个使用这个decorator的递归函数,但问题是我真的不知道允许我终止递归的基本情况。我能做到这一点的非直接方式是什么?我的主要问题是:是否可以将任何实现上下文管理器协议的类转换为使用
contextmanager
decorator的函数?@lmiguelvargasf递归生成器函数只能解决一半问题;您还必须将
print
作为属性附加到您返回的任何东西上。是否愿意将嵌套语句从带有缩进的
更改为带有缩进的
?我敢肯定,如果你是的话,情况会好一些。不管怎样,如果我回到电脑前你已经回答了,我会整理一些东西并编辑答案。我非常感谢你提供的额外信息。我一定会查的。再次感谢。