Skip to content
Snippets Groups Projects

Corrections - Tom

Merged Tom Reclik requested to merge tom into master
1 file
+ 0
3
Compare changes
  • Side-by-side
  • Inline
+ 0
3
%% Cell type:markdown id: tags:
# Exceptions and Exception Handling
There are many circumstances where a program may behave in a way we do not expect.
For example:
* We receive an input that we do not expect and need to react.
* We are not quite sure if an operation will succeed and need to proceed with "caution" and react, if it fails
* Something goes wrong - the program crashes but we want to recover gracefully.
%% Cell type:code id: tags:
``` python
print(1/0)
```
%% Output
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In [1], line 1
----> 1 print(1/0)
ZeroDivisionError: division by zero
%% Cell type:markdown id: tags:
Obviously, we knew beforehand that we should not try to divide 1 by 0.
The program crashed when we did so anyway - but python gives us more information what happened and where. This is called a ***traceback***. This traceback can also include multiple function calls or classes to guide us to where the error occurred.
Python uses special objects called *exceptions*. There are many pre-defined exceptions (and we can define our own) that help the user not only to understand what is happening, but also to handle exceptions in the code.
In our case here, we get the special exception: ```ZeroDivisionError``` with the added explanation: ```division by zero```.
## Intercepting Exceptions
If we suspect that an operation fails, we can use a try-except clause to prevent a crash.
The general syntax is
```
try:
# our statement here
except <exception we want to catch>:
# how to handle the error
except <other exceptions>:
# other recovery procedures
else:
# if we need to do something when everything worked
finally:
# whatever we need to do after the try-except clause completes.
```
When we define a try-except block we should at least catch one exception. However, in case we need more elaborate error handling, we can catch several exceptions of different type and act accordingly.
For the example above, we could try: \
(```\n``` creates a new line)
%% Cell type:code id: tags:
``` python
try:
print(1/0)
except ZeroDivisionError as e:
print('Do not try to divide by zero! \n Caught exeption: {}'.format(e))
```
%% Output
Do not try to divide by zero!
Caught exeption: division by zero
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
Here, we intercept the specific exception ```DivisionZeroError``` and just print out a warning. Note that the program no longer crashes: The exception is being raised, we intercept it and do something with it. In a more elaborate program we would then do some error handling and either continue with the code, or end the program in a controlled way if we find we cannot continue in a meaningful way.
Note that we intercepted the inception as a new variable: ```as e```. \
This allows us to work with and inspect this exception. Here we just print it and obtain the same information we had in the traceback earlier.
If we want to catch several types of exeption that we want to treat in the same way, we can do:
```
try:
# code prone to failure
except (excepton_1, exception_2, ...., exception_n):
# error handling
```
## Raising Exceptions
In many cases, we come to a point where we need to let the user know that a situation occurred that should not have occured. In the simplest way, we could write a ```print``` statement and print out an error message and then terminate the program. \
However, it would be better to pass an exception to the upstream code (e.g. the code calling a function or using a class) so they can inspect the error, handle it - or let the program crash if they do not.
To raise an exception, we use the keyword ```raise```.
Python already has a comprehensive list of [pre-defined exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions). If necessary, we can define our own as well.
***Example***
We define a function that divides two integer numbers. If the numbers are not integers, we raise an exception.
%% Cell type:code id: tags:
``` python
def my_divide (a, b):
if type(a) is not int or type(b) is not int:
raise TypeError('At least one of the arguments is not an integer')
return a/b
# This is the intended use
result = my_divide(3,5)
print('The result is: {}'.format(result))
# This will crash:
result = my_divide('three',5)
print('The result is: {}'.format(result))
```
%% Output
The result is: 0.6
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [4], line 11
8 print('The result is: {}'.format(result))
10 # This will crash:
---> 11 result = my_divide('three',5)
12 print('The result is: {}'.format(result))
Cell In [4], line 3, in my_divide(a, b)
1 def my_divide (a, b):
2 if type(a) is not int or type(b) is not int:
----> 3 raise TypeError('At least one of the arguments is not an integer')
4 return a/b
TypeError: At least one of the arguments is not an integer
%% Cell type:markdown id: tags:
Here, we can see a more elaborate traceback.
We note:
* We have augmented the pre-defined exception ```TypeError``` with an optional message that we can pass along to give more information about the exception or why we have raised it. It is a ```TypeError```, because we only want our function to work on specific types and one of the arguments is not of the specific type.
* The Tracebak shows the line that lead to the error
```
---> 11 result = my_divide('three',5)
```
and then the line where the error actually occured. If we have more functions or classes, then we would have a longer traceback, going through all steps.
We now use the try-except clause to intercept this case:
%% Cell type:code id: tags:
``` python
def my_divide (a, b):
if type(a) is not int or type(b) is not int:
raise TypeError('At least one of the arguments is not an integer')
return a/b
# Avoid the crash:
try:
new_result = my_divide('three',5)
print('The result is: {}'.format(new_result))
except TypeError as e:
print('Caught an exception: {}'.format(e))
print('The result is: {}'.format(new_result))
```
%% Output
Caught an exception: At least one of the arguments is not an integer
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In [1], line 14
11 except TypeError as e:
12 print('Caught an exception: {}'.format(e))
---> 14 print('The result is: {}'.format(new_result))
NameError: name 'new_result' is not defined
%% Cell type:markdown id: tags:
Now we note that the ```new_result``` is not defined outside the block because it was used inside the ```try``` block.
(Note: if we had used ```result```, it would not have caused an error because we have used the variable ```result``` in an earlier cell of the notebook. Since the notebook keeps its state, we would re-use that variable. As mentioned before, Jupyter notebooks are convenient - but very prone to side-effects and subtle bugs.)
Either we better create the variable first (like below) - or we need to stop the program gracefully if we intend to continue with the result but cannot do so without it.
%% Cell type:code id: tags:
``` python
result = 0
try:
result = my_divide('three',5)
print('The result is: {}'.format(result))
except TypeError as e:
print('Caught an exception: {}'.format(e))
print('The result is: {}'.format(result))
```
%% Output
Caught an exception: At least one of the arguments is not an integer
The result is: 0
%% Cell type:markdown id: tags:
Now result is defined - but we need to make sure that the default value is useful. Since we have now intercepted the exception, the code no longer stops. If ```result = 0``` is not useful, we need to handle this case now as well...
Also, consider:
%% Cell type:code id: tags:
``` python
result = 0
try:
result = my_divide(3,0)
print('The result is: {}'.format(result))
except TypeError as e:
print('Caught an exception: {}'.format(e))
print('The result is: {}'.format(result))
```
%% Output
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In [9], line 3
1 result = 0
2 try:
----> 3 result = my_divide(3,0)
4 print('The result is: {}'.format(result))
5 except TypeError as e:
Cell In [7], line 4, in my_divide(a, b)
2 if type(a) is not int or type(b) is not int:
3 raise TypeError('At least one of the arguments is not an integer')
----> 4 return a/b
ZeroDivisionError: division by zero
%% Cell type:markdown id: tags:
Now the code crashes again!
.... we have intercepted the ```TypeError``` but not ```ZeroDivisionError```.
Let's add this too:
%% Cell type:code id: tags:
``` python
result = 0
try:
result = my_divide(3,0)
print('The result is: {}'.format(result))
except TypeError as e:
print('Caught a TypeError: {}'.format(e))
except ZeroDivisionError as e:
print('Do not divide by zero! {}'.format(e))
print('The result is: {}'.format(result))
```
%% Output
Do not divide by zero! division by zero
The result is: 0
%% Cell type:markdown id: tags:
In a file that holds the python code (not the notebook), we could use ```exit()``` to end the program ourselves.
Loading