finally and rethowing of exception in except, raise in python - python

try:
#error code
except Exception as e:
print 'error',e
raise miexp("malicious error")
#userdefined exception, miexp
finally:
print 'finally'
Why the output is in the following formats?
Output:
error
finally
malicious error
Actually I expected as:
error
malicious error
finally
Why so?

miexp("malicious error") isn't handled, therefore it will end the execution of the program. On the other hand, the finally block is guaranteed to be executed.
To ensure this Python executes the finally block before actually raising the exception. From the documentation:
If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause.

Related

Exceptions within exception handling in Python

I'm trying to understand how Python handles exceptions within exception handling. For example, consider the following:
try:
try:
1/0
finally:
raise Exception("Exception!")
except Exception as e:
print(e)
My understanding is that both exceptions thrown by this code (both the ZeroDivisionError and the generic exception thrown in the finally block) should be "handled" by the outside except block...but how does Python decide which one to assign to e? Running the code on my machine, it seems that Python chooses to assign the "most recent" exception (the one thrown in the finally block) to e.
Is this generally true? Also, in a case like this where multiple exceptions might be thrown inside of error handling that are all handled by an outer except block, is there a way for the outer except block to step through each of the errors separately?
The Python docs have this:
If an exception occurs during execution of the try clause, the
exception may be handled by an except clause. If the exception is not
handled by an except clause, the exception is re-raised after the
finally clause has been executed.
So in your example, you don't catch the inner exception, this causes the finally block to execute (before re-raising the original exception). The exception in finally kicks it to the outside block before there is a chance to re-raise the original exception. The outside block never sees the divide-by-zero exception.
This is similar to returning from a function in finally:
def ex_test():
try:
try:
1/0
finally:
return "finally"
except Exception as e:
print(e) # never gets here
ex_test()
# only prints "finally"
# never re-raises the exception
You can get some information about the original exception. From the docs:
When raising (or re-raising) an exception in an except or finally
clause context is automatically set to the last exception caught;
if the new exception is not handled the traceback that is eventually
displayed will include the originating exception(s) and the final
exception.
So:
try:
try:
1/0
finally:
raise Exception("Exception!")
except Exception as e:
print(e.__context__)
# prints: "division by zero"

Why are exceptions inside else in try-except-else not re-thrown?

I'm using try|except|else|finally in python.
If the code inside else throws an exception, I want my overall script to fail (after executing finally).
I'm finding that this is not happening. Exceptions inside else are being suppressed. Why?
MWE
import requests
def foo():
try:
resp = requests.get("https://example.com")
status = resp.status_code
assert resp.status_code < 300, "Bad status code"
except requests.exceptions.BaseHTTPError:
status = None
else:
print("Starting else branch")
# this should fail
# because the body is not json
print(f"Response body was {resp.json()}")
print("Finishing else branch")
finally:
# save the result persistently,
# regardless of if it was good or bad
with open('log.txt', 'w') as f:
f.write(str(status))
return status
print(f"foo() gracefully returned {foo()}")
I've tried in python 3.6.9, 3.9.5 and 3.8.
Desired behavior:
resp.json() throws a simplejson.errors.JSONDecodeError exception inside else, which is then caught, finally is run, but then the exception from else is re-thrown, so the overall script fails with a JSONDecodeError.
stdout shows:
starting else branch
and then an exception is thrown, with a traceback to resp.json().
The python docs for the else in try|except|else|finally says:
The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.
So it sounds like the purpose of the else clause is for putting code where you do not want exceptions to be caught. That's what I want. But it seems like exceptions in else are being caught.
(Note that in this example I want the AssertionError to result in the overall function failing, and the BaseHTTPError to be caught and handled gracefully. That doesn't really make sense for a real use case, but this is just a simple MWE.)
Actual behavior
stdout says:
Starting else branch
foo() gracefully returned 200
The JSONDecodeError exception inside the else is caught, finally is executed, and the code returns 200. That is, the exception in the else code was caught. So it seems the code in the else branch was protected by the try.
If I wanted JSONDecodeError to be caught for my .json() line, I would have just put it inside the try.
How do I make this script not catch the JSONDecodeError error?
What you're seeing is perfectly documented:
If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause. If the finally clause raises another exception, the saved exception is set as the context of the new exception. If the finally clause executes a return, break or continue statement, the saved exception is discarded:
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
https://docs.python.org/3/reference/compound_stmts.html#try
To have exceptions inside else thrown after finally is executed, you must not have a return inside finally. Unindent it to move it outside the finally branch.

python ignore whichever line that causes error and continue running the code after that line

I understand the try/except method. What I'm trying to do is:
try:
some_func1() #potentially raises error too
do_something_else() #error was raised
continue_doing_something_else() #continues here after handling error
except:
pass
In the above code, when the error is raised at do_something_else(), the error is handled but then the try statement is exited.
What I want is for python to continue whatever code that is after the line that causes the error. Assuming that an error can happen anywhere in the try statement so I can't just wrap the try/except around do_something_else() itself, is there a way to do this in python?
What you want to do (try with restart) is not possible in Python. Lisp can do it (http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html), and you can implement it in Scheme using call/cc.
Just put the code that you want to do after the possible exception after the except. You may want to use finally (see https://docs.python.org/3/tutorial/errors.html for the documentation):
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception()
finally:
continue_doing_something_else() #continues here after handling error or if no error occurs
if continue_doing_something_else() can also throw exceptions, then put that in a try/except too:
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception1()
try:
continue_doing_something_else()
except:
handle_exception2()
finally:
any_cleanup()
as a general rule, your exception handling should be kept as small in scope as possible while remaining sensible, consider excepting only the 'expected' exceptions and not all of them (e.g. except OSError: when attempting to open files)

finally expression in python

Running the following code:
click here
I got the next output:
3 ok 6 ok oops ok ok Boom
I don't understand why does he prints the bolded ok? he doesn't even enter the loop.
I would like to get in-depth understanding of how exceptions and finally in particular works.
Thanks in advance!
code in finally block is always executed before leaving the try-catch block. The code in finally block is executed even if an exception is caught.
For detailed explanation of exception handling in python, see python 3 documentation
The official spec is
If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause. If the finally clause raises another exception, the saved exception is set as the context of the new exception. If the finally clause executes a return or break statement, the saved exception is discarded:
So what happens in the fourth iteration of your loop is number is set to "a" and when you try to convert it to int an exception is raised. Since there is no matching except in the inner try block the exception is saved, the finally block is executed which gives the fourth ok output and then the saved exception is reraised and caught by the outer try block.

Faster way to avoid catching an exception

Is there a way to temporary stop catching some exception?
The point is that when you have more exceptions which are being catched in a code and you want to comment them because you want to see all the things printed when exception is raised (print exception is not sufficient), you have to comment try, except, code in except, finally, code in finally and change an indent of the code where the exception can be raised. This commenting is very time-consuming when you have to do it many times.
#try:
pel.check_one_destination()
#except Exception as e:
#pel.driver.save_screenshot('log.png')
#print e
You can just add raise keyword inside the except block and it will raise the caught exception back. Found it useful during testing.
Example -
try:
#code that leads to exception
except Exception as e:
#handle exception
raise #for testing what the exception was, and etc.

Categories