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.
Related
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.
I would like to execute a loop that attempts to run a block of code, and skips past any iterations that happen to fail. However I would also like to be able to pass in a debug flag, so that if one of the iterations fails the program crashes and the user can see the backtrace to help themselves see where it failed. Essentially, I want to do this:
debug = False # or True
for i in range(10):
if not debug:
try:
# many lines of code
except:
print(f'Case {i} failed')
else:
# many lines of code
However, I don't want to duplicate the #many lines of code. I could wrap them in a helper function and do precisely the structure I wrote above, but I'm going to have to pass around a bunch of variables that will add some complexity (aka unit tests that I don't want to write). Is there another way to do this?
Best solution shy of factoring out many lines of code to its own function (which is often a good idea, but not always) would be to unconditionally use the try/except, but have the except conditionally re-raise the exception:
debug = False # or True
for i in range(10):
try:
# many lines of code
except:
if debug:
raise # Bare raise reraises the exception as if it was never caught
print(f'Case {i} failed')
A caught exception reraised with a bare raise leaves no user-visible traces to distinguish it from an uncaught exception; the traceback is unchanged, with no indication of it passing through the except block, so the net effect is identical to your original code, aside from debug being evaluated later (not at all if no exception occurs) and many lines of code appearing only once.
Side-note: Bare except blocks are a bad idea. If nothing else, limit it to except Exception: so you're not catching and suppressing stuff like KeyboardInterrupt or SystemExit when not in debug mode.
Best way is to use a function, just one line more does not matter too much.
Otherwise, if there is no need to test debug, There is no need to repeat the else block. Because if statements in try don't raise error, then the it will execute and result will be shown normally.
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.
It is conventional to use pass statement in python like the following piece of code.
try:
os.makedirs(dir)
except OSError:
pass
So, 'pass' bascially does not do anything here. In this case, why would we still put a few codes like this in the program? I am confused. Many thanks for your time and attention.
It's for the parser. If you wrote this:
try:
# Code
except Error:
And then put nothing in the except spot, the parser would signal an error because it would incorrectly identify the next indentation level. Imagine this code:
def f(x):
try:
# Something
except Error:
def g(x):
# More code
The parser was expecting a statement with a greater indentation than the except statement but got a new top-level definition. pass is simply filler to satisfy the parser.
This is in case you want the code to continue right after the lines in the try block. If you won't catch it - it either skips execution until it is caught elsewhere - or fails the program altogether.
Suppose you're creating a program that attempts to print to a printer, but also prints to the standard output - you may not want it to file if the printer is not available:
try:
print_to_printer('hello world')
except NoPrinterError:
pass # no printer - that's fine
print("hello world")
If you would not use a try-catch an error would stop execution until the exception is caught (or would fail the program) and nothing would be printed to standard output.
The pass is used to tell the program what to do when it catches an error. In this particular case you're pretty much ignoring it. So you're running your script and if you experience an error keep going without worrying as to why and how.
That particular case is when you are definite on what is expected. There are other cases where you can break and end the program, or even assign the error to a variable so you can debug your program by using except Error as e.
try:
os.makedirs(dir)
except OSError:
break
or:
try:
os.makedirs(dir)
except OSError as e:
print(str(e))
try:
# Do something
except:
# again some code
# few more code
There are two uses of pass. First, and most important use :- if exception arises for the code under try, the execution will jump to except block. And if you have nothing inside the except block, it will throw IndentationError at the first place. So, to avoid this error, even if you have nothing to do when exception arises, you need to put pass inside except block.
The second use, if you have some more code pieces after the try-except block (e.g. again some code and few more code), and you don't put pass inside except, then that code piece will not be executed (actually the whole code will not be executed since compiler will throw IndentationError). So, in order to gracefully handle the scenario and tell the interpreter to execute the lines after except block, we need to put pass inside the except block, even though we don't want to do anything in case of exception.
So, here pass as indicated from name, handles the except block and then transfers the execution to the next lines below the except block.
The Python Tutorial states:
The try ... except statement has an optional else clause, which,
when present, must follow all except clauses. It is useful for code
that must be executed if the try clause does not raise an exception.
For example:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'cannot open', arg
else:
print arg, 'has', len(f.readlines()), 'lines'
f.close()
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.
Question 1> After reading the above document, I still don't get the idea why we cannot simply move the code from else clause into try clause.
Question 2> How does try clause can accidentally catch an exception since all those catches are done in the except clause, right?
You could put the else code in the try suite, but then you'd catch any exceptions that might be raised there. If you didn't intend that to happen, it would be "accidental," hence the wording of the document you linked to.
Best practice is to put as little code as possible in a try block, so that when an error occurs, you know what operation caused it and can handle it appropriately. If you have five lines of code in a try block and only expect one of them to ever raise an exception, your exception-handling code will be ill-prepared when an exception occurs in a line you didn't expect it to. Better in that case to let the exception be raised than handle it the wrong way.
If you move the code from the else into the try then that becomes part of the "critical path" which can raise an exception. If f.readlines() raises some sort of exception (perhaps an I/O error while reading the file because of a bad sector on the disk) then that error will be conflated with the one error that you currently catch. (Technically the "cannot open" error message would be wrong at that point ... because opening a file can succeed when reading it later fails; in fact opening it must succeed before you can even get an I/O error while processing it).
Normally you'd use a pattern more like:
foo = None
try:
# some code to access/load/initialize foo from an external source
except ...:
# handle various types of file open/read, database access, etc errors
else:
foo = something
... so that your subsequently run code and simply check if foo is None and use it or
work around it's unavailability in whatever way you see fit.
Answer for both questions is similar,
If you move code to the try clause, then you can not be sure from where the exception is coming from. Thus if you have another line of code that produces an unexpected IOError you could end searching for a problem where there is not.
So to better disect your code you want to simplify as much as possible the lines in the try making the catch as especific as possible.
1) Of course you could just move code from the else clause into the try clause. You could move it outside of the try block entirely, but this allows extra flexibility, and further modulation of the code. Also, perhaps specificity with the errors being caught. You could list a whole load of different exceptions that might be likely to happen, each with different statements. The stuff in the else clause would still only happen if no exception was raised, after the execution of the final line in the try block. e.g. printing a successful return message.
Also, try clauses add some extra CPU overhead with regards to garbage collection management and error tracing, so anything outside of the try clause is not protected in the same manner and may run more efficiently.
2) Error catching is quite specific. e.g. The except clause in your above example will only be run if an IOError is raised when running the f = open(arg,'r') line. If you want to catch any form of exception, use except Exception:.