Chapter 5: Exception Handling
What Is Exception Handling
Exception handling is error management.
It has three purposes. 
Handling the Zero Division Error Exception
Exception handling can be an easy or difficult task depending on how you want your program to flow and your creativity.
You might have scratched your head because of the word creativity.
Programming is all about logic, right? No.
The core purpose of programming is to solve problems.
A solution to a problem does not only require logic. 
It also requires creativity.
Have you ever heard of the phrase, “Think outside of the box?” 
Program breaking exceptions can be a pain, and they are often called bugs.
The solution to such problems is often elusive.
And you need to find a workaround or risk rewriting your program from scratch.
For example, you have a calculator program with this snippet of code when you divide:
>>> def div(dividend, divisor):
print(dividend / divisor)
>>> div(7, 0)
Traceback (recent call to come last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in div
ZeroDivisionError: division by zero
>>> _
Of course, division by zero is an impossible operation.
Because of that, Python stops the program since it does not know what you want to do when this is encountered.
It does not know any valid answer or response.
That being said, the problem here is that the error stops your program entirely.
To manage this exception, you have two options.
First, you can make sure to prevent such operation from happening in your program.
Second, you can let the operation and errors happen, but tell Python to continue your program.
Here is what the first solution looks like:
>>> def div(dividend, divisor):
if (divisor != 0):
print(dividend / divisor)
else:
print("Cannot Divide by Zero.")
>>> div(5, 0)
Cannot Divide by Zero.
>>> _
Here is what the second solution looks like:
>>> def div(dividend, divisor):
try:
print(dividend / divisor)
except:
print("Cannot Divide by Zero.")
>>> div(5, 0)
Cannot Divide by Zero.
>>> _
Remember the two core solutions to errors and exceptions.
One, prevent the error from happening.
Two, manage the aftermath of the error.
Using Try-Except Blocks
In the previous example, the try-except blocks were used to manage the error.
However, you or your user can still do something to screw your solution up.
For example:
>>> def div(dividend, divisor):
try:
print(dividend / divisor)
except:
print("Cannot Divide by Zero.")
>>> div(5, "a")
Cannot Divide by Zero.
>>> _
The statement prepared for the “except” block is not enough to justify the error that was created by the input.
Dividing a number by a string does not actually warrant a “Cannot Divide by Zero.” message.
For this to work, you need to know more about how to use except block properly.
First of all, you can specify the error that it will capture and respond to by indicating the exact exception.
For example:
>>> def div(dividend, divisor):
try:
print(dividend / divisor)
except ZeroDivisionError:
print("Cannot Divide by Zero.")
>>> div(5, 0)
Cannot Divide by Zero.
>>> div(5, "a")
Traceback (most recent call last):
File "<stdin>", line 1, <module>
File "<stdin>", line 3, in div
TypeError: unsupported operand type(s) for /: 'int' and 'str'
>>> _
Now, the error that will be handled has been specified.
When the program encounters the specified error, it will execute the statements written on the “except” block that captured it.
If no except block is set to capture other errors, Python will then step in, stop the program, and give you an exception.
But why did that happen?
When the example did not specify the error, it handled everything.
That is correct.
When the “except” block does not have any specified error to look out for, it will capture any error instead.
For example:
>>> def div(dividend, divisor):
try:
print(dividend / divisor)
except:
print("An error happened.")
>>> div(5, 0)
An error happened.
>>> div(5, "a")
An error happened.
>>> _
That is a better way of using the “except” block if you do not know exactly the error that you might encounter.