# Exception handling
{id: exceptions}

## Hierarchy of calls
{id: hierarchy-of-calls}

```
main()
    some_process()
        for filename in some_list:
            handle_file(filename)
                private_module.deal_with_file(filename)
                    private_module._helper_function(filename)
                       public_module.process_file(filename)
                           with open(filename) as fh:
                               pass
```


## Handling errors as return values
{id: handling-errors-as-return-values}

* Each function that fails returns some error indicator. `None` ? An object that has and attribute "error"?
* `None` would be bad as that cannot indicate different errors.
* Every called needs to check if the function returned error. If at any point we forget our system might run with hidden failures.


![](examples/exceptions/demo1.py)

* If we forget to check the result and pass it on, we might get some error in the code that is quite far from where the error actually happened

![](examples/exceptions/demo2.py)

* This can happen even if we don't pass the result around:

![](examples/exceptions/demo3.py)


## Handling errors as exceptions
{id: handling-errors-as-exceptions}

* Only need to explicitly check for it at the level where we know what to do with the problem.
* But: Do we want our pacemaker to stop totally after missing one beat? Probably not. Or better yet: not when it is in production.

![](examples/exceptions/demo4.py)

## A simple exception
{id: a-simple-exception}
{i: ZeroDivisionError}

{aside}

When something goes wrong, Python throws (raises) an exception. For example,
trying to divide a number by 0 won't work. If the exception is not
handled, it will end the execution.
{/aside}

{aside}

In some programming languages we use the expression "throwing an exception" in other languages the expression is "raising an exception".
I use the two expressions interchangeably.
{/aside}

{aside}

In the next simple example, Python will print the string before the division,
then it will throw an exception, printing it to the standard error that is
the screen by default. Then the script stops working and the
string "after" is not printed.
{/aside}

![](examples/exceptions/divide_by_zero.py)

## Prevention
{id: prevention}

{aside}
We might try to prevent the exceptions generated by the system, but even if succeed in preventing it, how do we indicate that there was an issue?
For example with the input?
{/aside}

![](examples/exceptions/preventing_divide_by_zero.py)


## Working on a list
{id: a-simple-exception-on-a-list}

{aside}

In a slightly more interesting example we have a list of values.
We would like to divide a number by each one of the values.

As you can see one of the values is 0 which will generate and exception.

The loop will finish early.
{/aside}

![](examples/exceptions/divide_by_zero_list.py)

{aside}
We can't repair the case where the code tries to divide by 0, but it would be nice
if we could get the rest of the results as well.
{/aside}


## Catch ZeroDivisionError exception
{id: catch-divide-by-zero}
{i: except}
{i: ZeroDivisionError}

{aside}
For that, we'll wrap the critical part of the code in a "try" block.
After the "try" block we need to provide a list of exception that are
caught by this try-block.
{/aside}

{aside}
You could say something like
"Try this code and let all the exceptions propagate, except of the ones I listed".
{/aside}

{aside}

As we saw in the previous example, the specific error is called ZeroDivisionError.
{/aside}

{aside}

If the specified exception occurs within the try: block, instead of the script ending,
only the try block end and the except: block is executed.
{/aside}

![](examples/exceptions/divide_by_zero_catch.py)


## Module to open files and calculate something
{id: module-to-open-file-and-calculate-something}

{aside}

Of course in the previous example, it would be probably
much easier if we just checked if the number was 0,
before trying to divide with it. There are many other cases
when this is not possible. For example it is impossible to
check if open a file will succeed, without actually trying
to open the file.
{/aside}

{aside}

In this example we open the file, read the first line which
is a number and use that for division.
{/aside}

{aside}

When the open() fails, Python throws an FileNotFoundError exception.
{/aside}
![](examples/exceptions/module.py)


## File for exception handling example
{id: files-for-exception-example}

{aside}

If we have a list of files and we would like to make sure
we process as many as possible without any problem caused
in the middle, we can catch the exception.
{/aside}

{aside}
We have the following list of files.
        Notice that "two.txt" is missing and "zero.txt" has a 0 in it.
{/aside}
![](examples/exceptions/zero.txt)
![](examples/exceptions/one.txt)

File two.txt is missing on purpose.

![](examples/exceptions/three.txt)




## Open files - exception
{id: exceptions-open-files}

![](examples/exceptions/open_list_of_files.py)

```
python open_list_of_files.py one.txt zero.txt two.txt three.txt
```

## Handle divide by zero exception
{id: exceptions-handle-divide-by-zero}
{i: try}
{i: except}
{i: ZeroDivisionError}

{aside}
Running this code will the ZeroDivisionError exception, but it will die with a FileNotFoundError exception.
{/aside}

![](examples/exceptions/handle_divide_by_zero.py)

```
python handle_divide_by_zero.py one.txt zero.txt two.txt three.txt
```

## Handle files - exception
{id: exceptions-handle-files}
{i: FileNotFoundError}

{aside}
We can add multiple "except" statement at the end of the "try" block and handle several exceptions. Each one in a different way.
{/aside}

![](examples/exceptions/handle_both_exceptions.py)

```
python handle_both_exceptions.py one.txt zero.txt two.txt three.txt
```


## Catch all the exceptions and show their type
{id: show-exception-type}
{i: Exception}

{aside}
We can also use the "except Exception" to catch all exceptions. In this case we might want to also print out the text and the type of the exception by ourselves.
{/aside}

![](examples/exceptions/show_exception_type.py)

```
python show_exception_type.py one.txt zero.txt two.txt three.txt
```

## List exception types
{id: list-exception-types}

{aside}
We can list more than one exceptions to be caught one after the other in a single "except" statement.
{/aside}

```
except (ZeroDivisionError, FileNotFoundError):
```
![](examples/exceptions/handle_list_of_exceptions.py)

```
python handle_list_of_exceptions.py one.txt zero.txt two.txt three.txt
```

## Hierarchy of Exceptions
{id: hierarchy-of-exceptions}

{aside}
There are many kinds of exceptions in Python and each module can define its own exception types as well.
On this page you'll find the list and hierarchy of exceptions in Python.
{/aside}

* [exceptions](https://docs.python.org/library/exceptions.html#exception-hierarchy)

## Order of exception handling - bad
{id: order-of-exception-handling-bad}

![](examples/exceptions/except_order_bad.py)

* Both exception are caught by the first `except` entry

## Order of exception handling - good
{id: order-of-exception-handling-good}

![](examples/exceptions/except_order_good.py)

* Always try to handle the more specific exceptions first


## How to raise an exception
{id: raise-an-exception}
{i: raise}
{i: throw}
{i: Exception}

{aside}
As you create more and more complex applications you'll reach a point where you write a function, probably in a module, that needs to report some error condition.
You can raise an exception in a simple way.
{/aside}

![](examples/exceptions/raise.py)

```
$ python raise.py 
Adding apple: 3
Exception: Amount of sugar must be positive. -1 was given.
Type: Exception
Adding banana: 2
```

## Raise ValueError exception
{id: raise-a-valueerror-exception}
{i: ValueError}

{aside}
You can be more specific with your error type and raise a ValueError.
{/aside}

![](examples/exceptions/raise_value_error.py)

```
$ python raise_value_error.py
Adding apple: 3
Exception: Amount of sugar must be positive. -1 was given.
Type: ValueError
Adding banana: 2
```

## Stack trace of exceptions
{id: stack-trace-of-exceptions}

![](examples/exceptions/stack_trace.py)

![](examples/exceptions/stack_trace.out)

* [traceback](https://docs.python.org/3/library/traceback.html)

## No need for exception to print Stack trace
{id: stack-trace}
{i: traceback}
{i: format_stack}

![](examples/other/print_stack_trace.py)

* [traceback](https://docs.python.org/3/library/traceback.html)

## Raise Exception from
{id: raise-exception-from}
{i: from}
{i: None}
{i: raise}

![](examples/exceptions/raise_from.py)

## Exercise: Exception int conversion
{id: exercise-exception-int-conversion}

* In the earlier example we learned how to handle both ZeroDivisionError and FileNotFoundError exceptions. Now try this


```
cd examples/exceptions
python handle_both_exceptions.py one.txt zero.txt two.txt text.txt three.txt
```
![](examples/exceptions/handle_both_exceptions.out)

* This will raise a `ValueError` exception before handling file three.txt
* Fix it by capturing the specific exception.
* Fix by capturing "all other exceptions".

![](examples/exceptions/text.txt)


## Exercise: Raise Exception
{id: exercise-exception-raise-exception}

* Write a function that expects a positive integer as its single parameter.
* Raise exception if the parameter is not a number.
* Raise a different exception if the parameter is not positive.
* Raise a different exception if the parameter is not whole number.



## Solution: Exception int conversion (specific)
{id: solution-exception-int-conversion-specific}

![](examples/exceptions/handle_3_exceptions.py)
![](examples/exceptions/handle_3_exceptions.out)

```
python handle_3_exceptions.py one.txt zero.txt two.txt three.txt
```

## Solution: Exception int conversion (all other)
{id: solution-exception-int-conversion-all-other}

![](examples/exceptions/handle_all_other_exceptions.py)
![](examples/exceptions/handle_all_other_exceptions.out)


## Solution: Raise Exception
{id: solution-exception-raise-exception}

![](examples/exceptions/positive.py)

