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和stdlibcontextlib
的其他作者编写的第三方模块。它既可用于将contextlib
特性向后移植到较旧版本的Python,也可用于试验Python未来版本的新特性。IIRC,在某一点上,他们有一个可重用的@contextmanager
版本,但由于一个无法干净解决的bug,他们将其删除了。为什么?你已经有了工作代码,现在你想把它包装成一个包装成contextmanager
的函数,而你已经有了一个实现上下文管理器协议的类…@ForceBru,这只是为了实验。老实说,我没有实际的理由,只是好奇是否有可能做到。@abarnet,我想实现一个使用这个decorator的递归函数,但问题是我真的不知道允许我终止递归的基本情况。我能做到这一点的非直接方式是什么?我的主要问题是:是否可以将任何实现上下文管理器协议的类转换为使用contextmanager
decorator的函数?@lmiguelvargasf递归生成器函数只能解决一半问题;您还必须将print
作为属性附加到您返回的任何东西上。是否愿意将嵌套语句从带有缩进的:
更改为带有缩进的:
?我敢肯定,如果你是的话,情况会好一些。不管怎样,如果我回到电脑前你已经回答了,我会整理一些东西并编辑答案。我非常感谢你提供的额外信息。我一定会查的。再次感谢。