使用
@contextlib.contextmanager()
创建装饰器时,我们必须编写
enter_action(...)
try:
yield ...
finally:
exit_action(...)
这 3 行只是为了(相当不美观)
try/yield/finally
结构。
为什么我们不能得到这样的东西呢?
enter_action(...)
with context_magic(...): # equivalent to try/yield/finally
exit_action(...)
这在技术上可行吗?
实际上,有一个简单的解决方案: 我们可以简单地将 try/finally 包装委托给自定义装饰器:
import contextlib
def short_ctxmanager(func):
@contextlib.contextmanager
def wrapped(*args, **kws):
gen = func(*args, **kws)
out = next(gen)
try:
yield out
finally:
try:
next(gen)
except StopIteration:
pass
else:
raise RuntimeError("Context manager cannot have more than 1 yield.")
return wrapped
@short_ctxmanager
def my_ctx():
print("enter")
yield
print("exit (always executed)")
def test():
with my_ctx():
raise RuntimeError("exit should still run")
test()
打印:
enter
exit (always executed)
Traceback (most recent call last):
File "/home/me/projects/ctx.py", line 29, in <module>
test()
File "/home/me/projects/ctx.py", line 27, in test
raise RuntimeError("exit should still run")
RuntimeError: exit should still run
CC 为什么在使用 @contextmanager 装饰器时需要“try-finally”?
我认为我最初在问题中想象的
with
东西可能不起作用,因为我看不出有什么办法可以中断它来运行调用者的代码,然后再返回到退出处理程序。只有yield
可以做到。
Contexts 提供一个目标(从
__enter__()
方法返回的值)和错误处理(通过 __exit__()
方法)。您的示例未处理这些功能。
因此,虽然可以设计一种可以运行此类示例的语言,但其上下文管理器的功能集将会减少。
在Python中,你可以定义一个上下文管理器。上下文管理器是一个定义
__enter__
和 __exit__
方法的类。例如:
class MyContextManager():
def __init__(self, obj):
self.obj = obj
def __enter__(self):
print(f'entering with object {self.obj}')
def __exit__(self, exc_type, exc_val, exc_tb):
print(f'exiting with object {self.obj}')
if __name__ == '__main__':
with MyContextManager(object()):
print('inside block')
MyContextManager
类的任何实例都可以通过with
语句来处理。 with
语句只是在执行__enter__
语句块之前调用with
方法,并在执行块之后调用__exit__
。 __exit__
方法将传递 with
块内可能发生的任何异常的类型、值和回溯。
现在,要实现 yield
的目标,实际上将其放入 __enter__
方法中可能会很有用,尽管这是否有效取决于某些条件。
def enter_action(*args, **kwargs):
""" enter action's implementation goes here"""
pass
def exit_action(*args, **kwargs):
""" exit action's implementation goes here"""
pass
class ContextMagic():
def __init__(self, *args,**kwargs): # take what ever arguments that you need
pass
def __enter__(self): # call a function or do whatever you want
enter_action()
def __exit__(self, e_type, e_val, e_tb):
exit_action()
if __name__ == '__main__':
with ContextMagic(args, kwargs):
# code goes here