Reputation: 9905
Long story short, what would be the right way to get second snippet of code to work exactly like the first one?
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
And that one, of course, would not have the right device set when I need it:
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
What would be the right way to achieve same behaviour in the second case as in first case?
Upvotes: 5
Views: 2146
Reputation: 89
The key to the failure of your code is that a with statement calls enter when its block, and exit when the block ends. Having a with Device block inside the enter of Context means that the return statement, even though inside the block, leaves it, thus triggering the exit of Device. You can see this course of action by adding prints inside of each special method.
There are several possible solutions to make it work:
with Context("myctx"), Device("cls_dvc"):
Upvotes: 4
Reputation: 6227
You need to create a Device object inside your Context class, call the Device object's __enter__
method in the Context __enter__
method, and call the Device object's __exit__
method in the Context __exit__
method. If there is an error, then you can either handle it in the Context __exit__
method or the Device __exit__
method, whichever is more appropriate.
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
Upvotes: 4
Reputation: 1706
I get the right output by putting the with Device()
manager inside the 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])
Upvotes: 1