如何在 python 中链接上下文管理器?
How to chain context managers in python?
长话短说,让第二段代码像第一段一样工作的正确方法是什么?
stack_device = [None]
stack_context = [None]
@contextlib.contextmanager
def device(device):
stack_device.append(device)
try:
yield
finally:
stack_device.pop()
@contextlib.contextmanager
def context(ctx):
stack_context.append(ctx)
try:
with device("dev"):
yield
finally:
stack_context.pop()
with context("myctx"):
print(stack_device[-1]) # -> dev
print(stack_context[-1]) # -> ctx
而那个,当然,在我需要的时候没有正确的设备设置:
stack_device = [None]
stack_context = [None]
class Device():
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return
def __exit__(self, type, value, traceback):
stack_device.pop()
class Context():
def __init__(self, ctx):
self.ctx = ctx
def __enter__(self):
with Device("cls_dvc"):
stack_context.append(self.ctx)
return
def __exit__(self, type, value, traceback):
stack_context.pop()
with Context("myctx"):
print(stack_device[-1]) # -> None !!!
print(stack_context[-1]) # -> myctx
在第二种情况下实现与第一种情况相同的行为的正确方法是什么?
我通过将 with Device()
管理器放在 with Context()
中得到正确的输出。
stack_device = [None]
stack_context = [None]
class Device():
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return
def __exit__(self, type, value, traceback):
stack_device.pop()
class SubContext():
def __init__(self, ctx):
self.ctx = ctx
def __enter__(self):
stack_context.append(self.ctx)
return
def __exit__(self, type, value, traceback):
stack_context.pop()
class Context:
def __init__(self, ctx):
self.ctx = SubContext(ctx)
self.device = Device('dev')
def __enter__(self):
self.ctx.__enter__()
self.device.__enter__()
def __exit__(self, type, value, traceback):
self.ctx.__exit__(type, value, traceback)
self.device.__exit__(type, value, traceback)
with Context("myctx"):
print(stack_device[-1])
print(stack_context[-1])
您需要在 Context class 中创建一个 Device 对象,在 Context __enter__
方法中调用 Device 对象的 __enter__
方法,并调用 Device 对象的 __exit__
方法在 Context __exit__
方法中。如果出现错误,那么您可以在 Context __exit__
方法或 Device __exit__
方法中处理,哪个更合适。
stack_device = [None]
stack_context = [None]
class Device:
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return self
def __exit__(self, err_type, err_value, traceback):
stack_device.pop()
class Context:
def __init__(self, ctx):
self.ctx = ctx
self.cls_dvc = Device("cls_dvc")
def __enter__(self):
self.cls_dvc.__enter__()
stack_context.append(self.ctx)
return self
def __exit__(self, err_type, err_value, traceback):
stack_context.pop()
self.cls_dvc.__exit__(err_type, err_value, traceback)
with Context("myctx"):
print(stack_device[-1]) # -> cls_dvc
print(stack_context[-1]) # -> myctx
代码失败的关键是 with 语句调用 enter当它的块时,退出当块结束时。在 Context 的 enter 中有一个 with Device 块意味着 return 语句,即使在块内,也会离开它,从而触发 exit 的设备。您可以通过在每个特殊方法中添加打印来查看此操作过程。
有几种可能的解决方案可以使其发挥作用:
- 如果上下文管理器是独立的,就像在 Robert Kearns 的解决方案中一样,您可以使用标准 Python 链接从左开始按顺序创建它们,而不是创建一个超级上下文来创建您的两个上下文向右:
with Context("myctx"), Device("cls_dvc"):
- 如果您需要访问 Context class 中的设备属性(按照您的方式链接它们的唯一原因),那么您有两个解决方案:
- 重新设计您的 class 结构,以便单个 __enter__ 启动两个上下文,单个 __exit__ 清除两个上下文.在你的设计中,只需要将 Context 实现为上下文管理器,Device 可以是常规的 class.
- 如果你需要将 Device 保持为独立的上下文管理器(例如,到在其他地方单独使用它),那么 Jack Taylor 的解决方案就是您所需要的。请注意,Python 中有几种情况(例如,open("filename"))可以同时用作 "regular" 和 "context managers"。在这种情况下,所有逻辑都在常规方法中,而 __enter__ 和 __exit__ 仅包含对常规方法的调用方法,因此您不必直接调用特殊方法,如提供的答案
长话短说,让第二段代码像第一段一样工作的正确方法是什么?
stack_device = [None]
stack_context = [None]
@contextlib.contextmanager
def device(device):
stack_device.append(device)
try:
yield
finally:
stack_device.pop()
@contextlib.contextmanager
def context(ctx):
stack_context.append(ctx)
try:
with device("dev"):
yield
finally:
stack_context.pop()
with context("myctx"):
print(stack_device[-1]) # -> dev
print(stack_context[-1]) # -> ctx
而那个,当然,在我需要的时候没有正确的设备设置:
stack_device = [None]
stack_context = [None]
class Device():
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return
def __exit__(self, type, value, traceback):
stack_device.pop()
class Context():
def __init__(self, ctx):
self.ctx = ctx
def __enter__(self):
with Device("cls_dvc"):
stack_context.append(self.ctx)
return
def __exit__(self, type, value, traceback):
stack_context.pop()
with Context("myctx"):
print(stack_device[-1]) # -> None !!!
print(stack_context[-1]) # -> myctx
在第二种情况下实现与第一种情况相同的行为的正确方法是什么?
我通过将 with Device()
管理器放在 with Context()
中得到正确的输出。
stack_device = [None]
stack_context = [None]
class Device():
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return
def __exit__(self, type, value, traceback):
stack_device.pop()
class SubContext():
def __init__(self, ctx):
self.ctx = ctx
def __enter__(self):
stack_context.append(self.ctx)
return
def __exit__(self, type, value, traceback):
stack_context.pop()
class Context:
def __init__(self, ctx):
self.ctx = SubContext(ctx)
self.device = Device('dev')
def __enter__(self):
self.ctx.__enter__()
self.device.__enter__()
def __exit__(self, type, value, traceback):
self.ctx.__exit__(type, value, traceback)
self.device.__exit__(type, value, traceback)
with Context("myctx"):
print(stack_device[-1])
print(stack_context[-1])
您需要在 Context class 中创建一个 Device 对象,在 Context __enter__
方法中调用 Device 对象的 __enter__
方法,并调用 Device 对象的 __exit__
方法在 Context __exit__
方法中。如果出现错误,那么您可以在 Context __exit__
方法或 Device __exit__
方法中处理,哪个更合适。
stack_device = [None]
stack_context = [None]
class Device:
def __init__(self, device):
self.device = device
def __enter__(self):
stack_device.append(self.device)
return self
def __exit__(self, err_type, err_value, traceback):
stack_device.pop()
class Context:
def __init__(self, ctx):
self.ctx = ctx
self.cls_dvc = Device("cls_dvc")
def __enter__(self):
self.cls_dvc.__enter__()
stack_context.append(self.ctx)
return self
def __exit__(self, err_type, err_value, traceback):
stack_context.pop()
self.cls_dvc.__exit__(err_type, err_value, traceback)
with Context("myctx"):
print(stack_device[-1]) # -> cls_dvc
print(stack_context[-1]) # -> myctx
代码失败的关键是 with 语句调用 enter当它的块时,退出当块结束时。在 Context 的 enter 中有一个 with Device 块意味着 return 语句,即使在块内,也会离开它,从而触发 exit 的设备。您可以通过在每个特殊方法中添加打印来查看此操作过程。
有几种可能的解决方案可以使其发挥作用:
- 如果上下文管理器是独立的,就像在 Robert Kearns 的解决方案中一样,您可以使用标准 Python 链接从左开始按顺序创建它们,而不是创建一个超级上下文来创建您的两个上下文向右:
with Context("myctx"), Device("cls_dvc"):
- 如果您需要访问 Context class 中的设备属性(按照您的方式链接它们的唯一原因),那么您有两个解决方案:
- 重新设计您的 class 结构,以便单个 __enter__ 启动两个上下文,单个 __exit__ 清除两个上下文.在你的设计中,只需要将 Context 实现为上下文管理器,Device 可以是常规的 class.
- 如果你需要将 Device 保持为独立的上下文管理器(例如,到在其他地方单独使用它),那么 Jack Taylor 的解决方案就是您所需要的。请注意,Python 中有几种情况(例如,open("filename"))可以同时用作 "regular" 和 "context managers"。在这种情况下,所有逻辑都在常规方法中,而 __enter__ 和 __exit__ 仅包含对常规方法的调用方法,因此您不必直接调用特殊方法,如提供的答案