|
| 1 | +# Catching exceptions |
| 2 | + |
| 3 | +Not all exceptional situations can be avoided during writing the program. For example, consider the following function: |
| 4 | + |
| 5 | +```python |
| 6 | +def input_float(prompt): |
| 7 | + return float(input(prompt)) |
| 8 | +``` |
| 9 | +The purpose of this function is to read the actual number from the keyboard. However, we don't know what the user will enter. If it gives a string that cannot be converted to a real number, the program will be aborted. This is not correct behavior. In this situation, the correct program should inform the user that the entered text is incorrect and, for example, ask him to enter it again. |
| 10 | + |
| 11 | +In order to be able to program the program behavior in exceptional situations, the following structure is used: |
| 12 | + |
| 13 | +```python |
| 14 | +try: |
| 15 | + a block of commands that can cause an exception |
| 16 | +except ExceptionType: |
| 17 | + a block of commands performed on exceptions |
| 18 | +except DifferentExceptionType: |
| 19 | + a block of commands executed in the case of another type of exception |
| 20 | +``` |
| 21 | +If **`try`** is used, there must be at least one **`except`** block. |
| 22 | + |
| 23 | +Using this, a function that asks user to input a number could look like this: |
| 24 | + |
| 25 | +```python |
| 26 | +def input_float(prompt): |
| 27 | + try: |
| 28 | + return float(input(prompt) |
| 29 | + except ValueError: |
| 30 | + print ("Invalid number. I return 0.") |
| 31 | + return 0. |
| 32 | +``` |
| 33 | +This function calls the function `input` that asks for a string. Then that string is converted to type `float`. The conversion result is returned as the function result. If any of these steps fails, an exception will be thrown. If it is an exception of the type `ValueError` (in this case it may appear due to a failed conversion), it will be *caught* and an appropriate message will be printed and the function will return the value 0. |
| 34 | + |
| 35 | +Exceptions do not have to be caught in the same function as they appeared. Consider the following program: |
| 36 | + |
| 37 | +```python |
| 38 | +def input_float(prompt): |
| 39 | + return float(input(prompt)) |
| 40 | + |
| 41 | +def ask_number(): |
| 42 | + return input_float("Enter a number:") |
| 43 | + |
| 44 | +try: |
| 45 | + number = ask_number() |
| 46 | + result = 10 + number |
| 47 | +except ValueError: |
| 48 | + result = 0. |
| 49 | + |
| 50 | +print (number) |
| 51 | +``` |
| 52 | +If an incorrect value is entered, the function `input_float` will be aborted immediately. The function `ask_number` will also be interrupted. However, the entire program will not fail, since the exception is caught in the main code that calls the function `ask_number`. |
| 53 | + |
| 54 | +The **`try... except`** construct may additionally have an **`else`** block that is called when no exception occurs. E.g: |
| 55 | + |
| 56 | +```python |
| 57 | +try: |
| 58 | + number = ask_number() |
| 59 | + result = 10 + number |
| 60 | + except ValueError: |
| 61 | + result = 0. |
| 62 | + else: |
| 63 | + print("Thank you") |
| 64 | +``` |
| 65 | +The *Thank you* message will only be printed if the correct real number has been entered. |
| 66 | + |
| 67 | +Only exceptions of the given type are caught by the **`except`** command . Other exceptions are ignored by it and will either cause the program to stop or be caught elsewhere. Consider the following code: |
| 68 | + |
| 69 | +```python |
| 70 | +def input_float(prompt): |
| 71 | + try: |
| 72 | + return float(input(prompt)) |
| 73 | + except ValueError: |
| 74 | + print ("Invalid number. Returning 0.") |
| 75 | + return 0. |
| 76 | + |
| 77 | +try: |
| 78 | + number = input_float("Enter a number:") |
| 79 | +except KeyboardInterrupt: |
| 80 | + number = -1 |
| 81 | +``` |
| 82 | +If the `ValueError` exception occurs in the function `input_float`, it prints a message and returns the value 0. However, pressing `Ctrl + C` or `Ctrl + Delete` will generate an exception `KeyboardInterrupt` that will break the function. However, it will be caught at the point where this function was called, and the variable `number` will be set to –1. |
| 83 | + |
| 84 | +## Better to ask for forgiveness than permission |
| 85 | + |
| 86 | +In Python, exceptions are normal and should be used. Let's look at a function that returns the inverse of the number given as its argument: |
| 87 | + |
| 88 | +```python |
| 89 | +def inverse(x): |
| 90 | + return 1 / x |
| 91 | +``` |
| 92 | + |
| 93 | +Suppose we want to protect ourselves in case the given argument is 0 and we want to return the value 0 in this case (which of course is mathematically incorrect, but may be useful in some situations). We can do this in two ways: |
| 94 | + |
| 95 | +```python |
| 96 | +def inverse (x): |
| 97 | + if x == 0: |
| 98 | + return 0 |
| 99 | + else : |
| 100 | + return 1 / x |
| 101 | +``` |
| 102 | +or |
| 103 | + |
| 104 | +```python |
| 105 | +def inverse (x): |
| 106 | + try: |
| 107 | + return 1 / x |
| 108 | + except ZeroDivisionError: |
| 109 | + return 0 |
| 110 | +``` |
| 111 | + |
| 112 | +In the first case, "we ask for permission", i.e. we use a condition to check whether the operation can be performed. In the second, we "beg for forgiveness", that is, we simply perform the operation, and in case of problems, we solve them. Generally the second way is better. In case of more complex programs, it results in more readable code and it is easier to add handling of other exceptions, which we are not able to predict at the time of writing the original version of the program. In addition, we are not able to check everything in advance, and even if it is possible, it may be unreliable: this applies in particular to file operations, which will be discussed in the next lecture. For example, if we first check if a given file exists and then open it, there is a non-zero probability that within microseconds between checking the existence of the file and opening it, it will be deleted (the probability is not so small if, for example, the computer's disk is damaged). In such a situation, we will receive permission to perform the operation (the condition will confirm the existence of the file), but the operation itself will fail. By using exception catching, we protect ourselves against such a situation. Another reason why "begging for forgiveness" is better is the performance of the program: when checking the condition first, we need to perform some operations that are likely to be repeated afterwards. When we use exceptions, we only perform a given operation once. The difference may be a few microseconds but it becomes noticeable when a given function is repeated several dozen million times (which is not unusual in larger programs for data analysis or scientific calculations). |
| 113 | + |
| 114 | +## How to clean up a mess? |
| 115 | + |
| 116 | +The instruction **`try`** has one more, optional clause, which is used to define actions to perform the necessary cleanups in all respects. E.g: |
| 117 | + |
| 118 | +```python |
| 119 | +try: |
| 120 | + number = float('two') # this will cause an exception ValueError |
| 121 | +finally: |
| 122 | + print('Goodbye, world!') |
| 123 | +``` |
| 124 | +The clause **`finally`** is executed whether or not an exception occurs. The code in this block is also executed when the try block is "exited" with the `break` or `return` statements. |
| 125 | + |
| 126 | +The **`try`** statement must have at least one **`except`** block or one **`finally`** block. |
| 127 | + |
| 128 | + |
| 129 | +<hr /> |
| 130 | +<p id="copyright">Published under <a class="external" rel="nofollow" href="https://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution-NonCommercial-ShareAlike</a> license.</p> |
0 commit comments