Since I've learned of exception-handling the first time (not in Python), I was under the impression that when you begin a try block, it's like you start writing in a sandbox: if an exception occurs, everything that happened inside the try block will be like it never happened. To my naive surprise, I noticed this isn't true, or not in the way I thought, at least in Python. Here's my experiment in Python:
>>> a = range(5)
>>> a
[0, 1, 2, 3, 4]
>>> try:
... a.append(5)
... oops
... except:
... raise
...
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'oops' is not defined
>>> print a
[0, 1, 2, 3, 4, 5]
As you can see, I changed the list inside the try block, then trigger an error, which is raised. I was expecting to see the list in its original form, [0, 1, 2, 3, 4]
, but the a.append(5)
persisted.
Were my expectations wrong in the first place? Maybe partially wrong expectations(there may be a sandbox, but it doesn't act like that)?
If the exception occurs, and nothing is done to resolve the underlying cause, there is no retry counter or such to stop the loop.
With try and except , even if an exception occurs, the process continues without terminating.
If an exception occurs during execution of the try clause, the rest of the clause is skipped. Then, if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try/except block.
By putting a BEGIN-END block with an exception handler inside of a loop, you can continue executing the loop if some loop iterations raise exceptions. You can still handle an exception for a statement, then continue with the next statement. Place the statement in its own subblock with its own exception handlers.
You just discovered that exceptions are not a silver bullet for error handling.
Exceptions do not protect you from state changes... whatever was completed without errors before the exception is thrown will have to be undone. This is how exception work in Python, C++, Java and many other languages as well.
Sometimes you may have a sort of "external" general protection: for example if all you do is changes to a database that supports transactions then you can have the top-level catching statement to do a "rollback" instead of committing the changes and you get the protection you're looking for. If there is no such a natural "wall" protecting from partial state change then things are much more difficult to handle correctly.
The very reason that operations that have been completed will not be undone is what makes the use of exceptions not trivial as the complexity of the problems scales up.
Normally code can be classified exception "safe" at several levels:
In case of an exception everything is ruined and not even a clean exit or restart is possible. This is what is normally classified as NOT exception safe.
In case of an exception the code will not complete its job, and the state of the subsystem (class instance, library) is invalid. You however can restart safely (e.g. you can destroy the instance or reinitialize the library). This is the very minimum exception safety.
In case of an exception the code will not complete its job, and the state of the subsystem will be valid but unspecified. The calling code may try to inspect the current state and keep using the subsystem instead of reinitializing it, for example. Just a little better than 2.
In case of an exception the code will do nothing, leaving the program state untouched. So either the request is completed without errors or an error signal is returned to the caller and nothing has been altered. This is of course the best behavior.
The biggest problem with exception handling is that even if you have two very safe type-4 pieces A
and B
still the simple sequential composition AB
is not safe because in case of a problem in B
you must also undo whatever A
has completed.
Also if it's possible to get an exception when performing 1/A
(i.e. when un-doing what A
was able to complete) then you're in BIG trouble, because you cannot neither do B
, nor restoring the state as it was before (this means it's just impossible to implement AB
as a type-4 operation).
In other words good bricks won't make trivial making good constructions (in respect to exception safety).
Your expectation is wrong in just about any language that supports exceptions - there is no all-or-nothing semantics associated with try blocks (although you may have transactional concepts in some languages, e.g. if there is support for transactional memory).
It's only that the part of the try block after the exception is not executed anymore.
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