|
| 1 | +# pycobytes[29] := Expect the Exception |
| 2 | +<!-- #SQUARK live! |
| 3 | +| dest = issues/(issue)/29 |
| 4 | +| title = Expect the Exception |
| 5 | +| head = Expect the Exception |
| 6 | +| index = 29 |
| 7 | +| tags = |
| 8 | +| date = 2025 May 16 |
| 9 | +--> |
| 10 | + |
| 11 | +> *A good programmer designs a ship that can't sink, then designs the lifeboats for when it does.* |
| 12 | +
|
| 13 | +Hey pops! |
| 14 | + |
| 15 | +Ahh! A syntax error! Nooo! |
| 16 | + |
| 17 | +What’s a `SyntaxError` anyway? |
| 18 | + |
| 19 | +An **error**, technically speaking, refers to anything that’s wrong in your program – unintended behaviour, unsuitable data, inaccurate logic, etc. Some – but not all – errors can interrupt the flow of the program, which you usually see as a crash. |
| 20 | + |
| 21 | +```bash |
| 22 | +C:Users/Rick > python main.py |
| 23 | +Hello world! |
| 24 | +SyntaxError: unmatched ')' |
| 25 | +``` |
| 26 | + |
| 27 | +When this happens, it means an **Exception** has been raised. If unhandled, these terminate the program and output that lovely error message. |
| 28 | + |
| 29 | +```py |
| 30 | +Traceback (most recent call last): |
| 31 | + File "C:/Users/Rick/demo.py", line 39, in <module> |
| 32 | + handler.handle() |
| 33 | + File "C:/Users/Rick/demo.py", line 31, in handle |
| 34 | + respond(*args, **kwargs) |
| 35 | +TypeError: handler.<locals>.handle.<locals>.respond() missing 1 required positional arguments: 'status' |
| 36 | +``` |
| 37 | + |
| 38 | +We call this output a **stack trace**, since it shows all the layers that led to the exception being raised. This is a brilliant tool for debugging, and it’s why other people online will ask for your entire error output (rather than just the single line with the message) when helping you debug. |
| 39 | + |
| 40 | +```py |
| 41 | +# what does this mean?? |
| 42 | +SyntaxError: EOL while scanning string literal |
| 43 | +``` |
| 44 | + |
| 45 | +You’ve likely encountered plenty of exceptions in your time programming – `IndexError`, `TypeError`, `ValueError`. |
| 46 | + |
| 47 | +It’s inevitable for errors to arise in a program, and this certainty only increases with complexity. Often we overlook error handling when we start out programming, but gracefully and *properly* handling errors is critical in production software. |
| 48 | + |
| 49 | +We can ‘catch’ and respond to errors using **try-except**: |
| 50 | + |
| 51 | +```py |
| 52 | +try: |
| 53 | + do_something() |
| 54 | +except: |
| 55 | + panic() |
| 56 | +``` |
| 57 | + |
| 58 | +If an exception is raised while running the code inside `try:`, then that code will terminate, but it won’t terminate the entire program. Instead, the code inside `except:` is ran. Here’s where we can put our error handling. |
| 59 | + |
| 60 | +```py |
| 61 | +>>> try: |
| 62 | + l = [0, 1, 2, 3, 4] |
| 63 | + print(l[5]) |
| 64 | + l.append(5) # never happens since previous line raises exception |
| 65 | + print("this never runs") |
| 66 | + |
| 67 | + except: |
| 68 | + print("l was not long enough, L") |
| 69 | + |
| 70 | +l was not long enough, L |
| 71 | +``` |
| 72 | + |
| 73 | +We can selectively respond to specific exception types by naming them: |
| 74 | + |
| 75 | +```py |
| 76 | +>>> try: |
| 77 | + n = int(input("What would you like to divide by? )) |
| 78 | + print(1 / n) |
| 79 | + |
| 80 | + except TypeError: |
| 81 | + print("Just enter a number, mate") |
| 82 | + |
| 83 | + except ZeroDivisonError: |
| 84 | + print("You can’t divide by 0, muppet") |
| 85 | + |
| 86 | +What would you like to divide by? 0 |
| 87 | +You can’t divide by 0, muppet |
| 88 | +``` |
| 89 | + |
| 90 | +And Python makes it easy for you to respond to multiple exceptions with the same block – just provide a `tuple` of those you want to catch: |
| 91 | + |
| 92 | +```py |
| 93 | +try: |
| 94 | + do_it() |
| 95 | +except (TypeError, ValueError): |
| 96 | + failed_to_do_it() |
| 97 | +except IndexError: |
| 98 | + database_empty() |
| 99 | +``` |
| 100 | + |
| 101 | +Remember the for-else loop? We have the same pattern here – an `else:` block under the try-except runs if *no* exception is raised. In other words, the code ran successfully: |
| 102 | + |
| 103 | +```py |
| 104 | +>>> try: |
| 105 | + print(0 ** 0) |
| 106 | + except: |
| 107 | + print("okk Python did not like that") |
| 108 | + else: |
| 109 | + print("guess we’re good to go then") |
| 110 | + |
| 111 | +guess we’re good to go then |
| 112 | +``` |
| 113 | + |
| 114 | +This is different to just putting the code after the try-except, because remember that |
| 115 | + |
| 116 | +```py |
| 117 | +try: |
| 118 | + print(0 / 0) |
| 119 | +except: |
| 120 | + print("error") |
| 121 | +else: |
| 122 | + print("no error") |
| 123 | + |
| 124 | +print("but this always runs, regardless of error or no error") |
| 125 | +``` |
| 126 | + |
| 127 | +Even though the exception is thrown, the program as a whole doesn’t stop, because we’ve now got the `except` block absorbing the exception. |
| 128 | + |
| 129 | +We can throw exceptions manually using the `raise` keyword: |
| 130 | + |
| 131 | +```py |
| 132 | +try: |
| 133 | + raise TypeError |
| 134 | +except: |
| 135 | + print("huh wonder how that happened") |
| 136 | +``` |
| 137 | + |
| 138 | +Usually this is followed by the `Exception` class (or object) you want to raise, but you can also use it blank: |
| 139 | + |
| 140 | +```py |
| 141 | +try: |
| 142 | + if database.empty: |
| 143 | + raise |
| 144 | +except: |
| 145 | + print("oh") |
| 146 | +``` |
| 147 | + |
| 148 | +You’d never actually write the following in production code (I hope), but you might have it as an intermediate during debugging: |
| 149 | + |
| 150 | +```py |
| 151 | +try: |
| 152 | + will_go_wrong() |
| 153 | +except: |
| 154 | + raise |
| 155 | +``` |
| 156 | + |
| 157 | +This of course does nothing since it just re-raises the error. But you might, say, comment out your usual error handling code and replace it with a `raise` if you wanted the program to crash and output the stack trace. |
| 158 | + |
| 159 | +```py |
| 160 | +try: |
| 161 | + work() |
| 162 | +except: |
| 163 | + # if game.state == "idle": |
| 164 | + # game.nonblock_debug() |
| 165 | + # else: |
| 166 | + # game.exit_on_error() |
| 167 | + raise |
| 168 | +``` |
| 169 | + |
| 170 | +And finally, if you need to use the exception you encounter as an object, assign to a variable with `as`: |
| 171 | + |
| 172 | +```py |
| 173 | +try: |
| 174 | +except IndexError as e: |
| 175 | + print("error encountered") |
| 176 | + print(e) # outputs error trace |
| 177 | + raise e # forwards exception |
| 178 | +``` |
| 179 | + |
| 180 | +These are the basics for expecting and capturing exceptions. How you handle them, of course, is a totally different story. (Pro tip: just printing the exception and ignoring it is NOT the solution.) |
| 181 | + |
| 182 | +In future, we’ll look at how you can define your own exceptions, and use them for sophisticated state and control flow management! |
| 183 | + |
| 184 | + |
| 185 | +<br> |
| 186 | + |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +<div align="center"> |
| 191 | + |
| 192 | +[](http://thecodelesscode.com/case/87) |
| 193 | + |
| 194 | +[*The Codeless Code*, Case 87](http://thecodelesscode.com/case/87) |
| 195 | + |
| 196 | +</div> |
| 197 | + |
0 commit comments