I think I've read that exceptions inside a with
do not allow __exit__
to be call correctly. If I am wrong on this note, pardon my ignorance.
So I have some pseudo code here, my goal is to use a lock context that upon __enter__
logs a start datetime and returns a lock id, and upon __exit__
records an end datetime and releases the lock:
def main(): raise Exception with cron.lock() as lockid: print('Got lock: %i' % lockid) main()
How can I still raise errors in addition to existing the context safely?
Note: I intentionally raise the base exception in this pseudo-code as I want to exit safely upon any exception, not just expected exceptions.
Note: Alternative/standard concurrency prevention methods are irrelevant, I want to apply this knowledge to any general context management. I do not know if different contexts have different quirks.
PS. Is the finally
block relevant?
Context managers allow you to allocate and release resources precisely when you want to. The most widely used example of context managers is the with statement. Suppose you have two related operations which you'd like to execute as a pair, with a block of code in between.
A context manager usually takes care of setting up some resource, e.g. opening a connection, and automatically handles the clean up when we are done with it. Probably, the most common use case is opening a file. The code above will open the file and will keep it open until we are out of the with statement.
The try-catch is the simplest method of handling exceptions. Put the code you want to run in the try block, and any Java exceptions that the code throws are caught by one or more catch blocks. This method will catch any type of Java exceptions that get thrown. This is the simplest mechanism for handling exceptions.
The __exit__
method is called as normal if the context manager is broken by an exception. In fact, the parameters passed to __exit__
all have to do with handling this case! From the docs:
object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
Note that
__exit__()
methods should not reraise the passed-in exception; this is the caller’s responsibility.
So you can see that the __exit__
method will be executed and then, by default, any exception will be re-raised after exiting the context manager. You can test this yourself by creating a simple context manager and breaking it with an exception:
DummyContextManager(object): def __enter__(self): print('Entering...') def __exit__(self, exc_type, exc_value, traceback): print('Exiting...') # If we returned True here, any exception would be suppressed! with DummyContextManager() as foo: raise Exception()
When you run this code, you should see everything you want (might be out of order since print
tends to end up in the middle of tracebacks):
Entering... Exiting... Traceback (most recent call last): File "C:\foo.py", line 8, in <module> raise Exception() Exception
The best practice when using @contextlib.contextmanager
was not quite clear to me from the above answer. I followed the link in the comment from @BenUsman.
If you are writing a context manager you must wrap the yield
in try-finally
block:
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource) >>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With