Let’s say I have a context manager that provides a resource that then mutates on exit:
from contextlib import contextmanager
@contextmanager
def context():
x = ['hi']
yield x
x[0] = 'there'
I found that if I want to make another context class that uses this, such that the context (before mutation) is valid, I have to pass it in:
class Example1:
def __init__(self, obj):
self.obj = obj
def use_obj(self):
print(self.obj)
def __enter__(self):
print("start")
return self
def __exit__(self, *exc):
print("end")
with context() as x:
with Example1(x) as y:
y.use_obj()
prints:
start
['hi']
end
However, what I don’t like is, let’s say that obj
is an internal detail of my class. I don’t want the user to have to define it beforehand and pass it in.
The only way I can figure how to do this is by calling the context manager’s __enter__()
explicitly:
class Example2:
def use_obj(self):
print(self.obj)
def __enter__(self):
print("start")
self.ctx = context()
self.obj = self.ctx.__enter__()
return self
def __exit__(self, *exc):
print("end")
self.ctx.__exit__(None, None, None)
with Example2() as y:
y.use_obj()
which also prints,
start
['hi']
end
For comparison, just as some other random attempt, the following doesn’t work because the context ends when self.obj
is created:
class Example3:
def use_obj(self):
print(self.obj)
def __enter__(self):
print("start")
with context() as x:
self.obj = x
return self
def __exit__(self, *exc):
print("end")
with Example3() as y:
y.use_obj()
which prints,
start
['there']
end
Okay, so my point is that Example2
is the right solution here. But, it’s really ugly. So my question is, is there a better way to write Example2
?
Have the thing that uses obj take it as a normal constructor argument, and have a convenience wrapper that supplies it:
from contextlib import contextmanager @contextmanager def context(): x = ['hi'] yield x x[0] = 'there' class ObjUser: def __init__(self, obj): self.obj = obj def use_obj(self): print(self.obj) @contextmanager def MakeObjUser(): with context() as obj: yield ObjUser(obj) with MakeObjUser() as y: y.use_obj()