Reputation: 165
I was wondering lately what was the reason behind not calling implicitly __exit__
when __enter__
raises exception?
Why it was designed in such way? I was implementing service runner class to be usable by 'with'
keyword and it turned out that __exit__
is never called.
Example:
class ServiceRunner(object):
def __init__(self, allocation_success):
self.allocation_success = allocation_success
def _allocate_resource(self):
print("Service1 running...")
print("Service2 running...")
# running service3 fails ...
if not self.allocation_success:
raise RuntimeError("Service3 failed!")
print("Service3 running...")
def _free_resource(self):
print("All services freed.")
def __enter__(self):
self._allocate_resource()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._free_resource()
Usage:
with ServiceRunner(allocation_success=True):
pass
try:
with ServiceRunner(allocation_success=False):
pass
except Exception as e:
print(e)
Output:
Service1 running...
Service2 running...
Service3 running...
All services freed.
and
Service1 running...
Service2 running...
Service3 failed!
Function __exit__
is not called. Service1 and Service2 are not freed.
I could move _allocate_resource()
to __init__
but then class is not very useful in such usage:
try:
runner = ServiceRunner(allocation_success=True)
except Exception as e:
print(e)
else:
with runner as r:
r.do()
with runner as r:
r.do()
Output:
Service1 running...
Service2 running...
Service3 running...
All services freed.
All services freed.
Services are not started again.
I could reimplement __enter__
to handle exceptions but it adds some boilerplate code to the function:
def __enter__(self):
try:
self._allocate_resource()
except Exception as e:
self.__exit__(*sys.exc_info())
raise e
Is it the best solution?
Upvotes: 5
Views: 1831
Reputation: 1044
If you've failed to enter a context, there's no reason to attempt to exit it, i.e. if you've failed to allocate a resource, there's no reason to attempt to release it.
IIUC, what you're looking for is simply:
try:
with ServiceRunner() as runner:
runner.do()
except Exception as e:
print(e)
Upvotes: 3