Skip to content

Commit c338e74

Browse files
committed
work on exceptions
1 parent edda8c1 commit c338e74

1 file changed

Lines changed: 185 additions & 49 deletions

File tree

exceptions.md

Lines changed: 185 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
11
# Exceptions
22

3-
So far we have made programs that ask the user to enter a string.
3+
So far we have made programs that ask the user to enter a string, and
4+
we also know how to convert that to an integer.
45

56
```py
67
text = input("Enter something: ")
7-
print("Your text twice:", text*2)
8-
```
9-
10-
That works.
11-
12-
```
13-
Enter something: hello
14-
Your text twice: hellohello
15-
```
16-
17-
But if the user enters a number, it's not going to be multiplied by two.
18-
19-
```
20-
Enter something: 3
21-
Your text twice: 33
22-
```
23-
24-
Let's use `int` to convert that to an integer:
25-
26-
```py
27-
text = input("Enter a number: ")
288
number = int(text)
29-
print("Your number twice:", number*2)
9+
print("Your number doubled:", number*2)
3010
```
3111

32-
That works...
12+
That works.
3313

3414
```
3515
Enter a number: 3
3616
Your number twice: 6
3717
```
3818

39-
...unless the user does not enter a number.
19+
But that doesn't work if the user does not enter a number.
4020

4121
```py
4222
Enter a number: lol
@@ -53,7 +33,8 @@ So how can we fix that?
5333
In the previous example we got a ValueError. ValueError is an
5434
**exception**. In other words, ValueError is an error that can occur
5535
in our program. If an exception occurs, the program will stop and we
56-
get an error message.
36+
get an error message. The interactive prompt will display an error
37+
message and keep going.
5738

5839
```py
5940
>>> int('lol')
@@ -63,28 +44,21 @@ ValueError: invalid literal for int() with base 10: 'lol'
6344
>>>
6445
```
6546

66-
Exceptions are classes, just like int and str. We'll talk more about
67-
classes later in this tutorial.
47+
Exceptions are [classes](classes.md).
6848

6949
```py
70-
>>> str
71-
<class 'str'>
72-
>>> int
73-
<class 'int'>
7450
>>> ValueError
7551
<class 'ValueError'>
7652
>>>
7753
```
7854

79-
You can also create an exception. You won't get an error message by doing
55+
We can also create exceptions. We won't get an error message by doing
8056
that, but we'll use this for displaying our own error messages later.
8157

8258
```py
8359
>>> the_problem = ValueError('oh no')
8460
>>> the_problem
8561
ValueError('oh no',)
86-
>>> type(the_problem)
87-
<class 'ValueError'>
8862
>>>
8963
```
9064

@@ -124,6 +98,21 @@ TypeError: unsupported operand type(s) for +: 'int' and 'str'
12498
>>>
12599
```
126100

101+
Exceptions always interrupt the code even if we catch them. Here the
102+
print never runs because it's after the error but inside the `try`
103+
block. Everything after the try block runs normally.
104+
105+
```py
106+
>>> try:
107+
... 123 + 'hello'
108+
... print("This doesn't get printed.")
109+
... except TypeError:
110+
... print("Oops!")
111+
...
112+
Oops!
113+
>>>
114+
```
115+
127116
Does an `except ValueError` also catch TypeErrors?
128117

129118
```py
@@ -163,7 +152,28 @@ wrong type
163152

164153
Seems to be working.
165154

166-
It's also possible to catch an exception, and store it in a variable:
155+
Catching `Exception` will catch all errors. We'll learn more about why
156+
it does that in a moment.
157+
158+
```py
159+
>>> try:
160+
... 123 + 'hello'
161+
... except Exception:
162+
... print("Oops!")
163+
...
164+
Oops!
165+
>>> try:
166+
... int('lol')
167+
... except Exception:
168+
... print("Oops!")
169+
...
170+
Oops!
171+
>>>
172+
```
173+
174+
It's also possible to catch an exception and store it in a variable.
175+
Here we are catching an exception that Python created and storing it in
176+
`our_error`.
167177

168178
```py
169179
>>> try:
@@ -178,10 +188,48 @@ TypeError("unsupported operand type(s) for +: 'int' and 'str'",)
178188
>>>
179189
```
180190

191+
## When should we catch exceptions?
192+
193+
Never do things like this:
194+
195+
```py
196+
try:
197+
# many lines of code
198+
except Exception:
199+
print("Oops! Something went wrong.")
200+
```
201+
202+
There's many things that can go wrong in the `try` block. If something
203+
goes wrong all we have is an oops message that doesn't tell us which
204+
line caused the problem. This makes fixing problems a lot harder. If we
205+
want to catch exceptions we need to be specific about what exactly we
206+
want to catch instead of catching everything we can.
207+
208+
There's nothing wrong with doing things like this:
209+
210+
```py
211+
try:
212+
with open('some file', 'r') as f:
213+
content = f.read()
214+
except OSError: # we can't read the file but we can work without it
215+
content = some_default_content
216+
```
217+
218+
Usually catching errors that the user has caused is also a good idea:
219+
220+
```py
221+
text = input("Enter a number: ")
222+
try:
223+
number = int(text)
224+
except ValueError:
225+
print("'%s' is not a number." % text, file=sys.stderr)
226+
sys.exit(1)
227+
print("Your number doubled is %d." % (number * 2))
228+
```
229+
181230
## Raising exceptions
182231

183-
When working on something that other programmers are going to use,
184-
you may end up doing something like this:
232+
Sometimes you may end up doing something like this:
185233

186234
```py
187235
if number < 0:
@@ -190,10 +238,10 @@ if number < 0:
190238

191239
But that's not ideal. If there is an error, the code prints an error
192240
message but it still keeps running. People using your code also don't know
193-
which line in their code caused the error.
241+
which line in **their** code caused the error.
194242

195-
Instead you can **raise** an exception yourself. Sometimes this is also
196-
called **throwing** an exception.
243+
Instead you can **raise an exception** yourself. Sometimes this is also
244+
called **throwing an exception**.
197245

198246
```py
199247
if number < 0:
@@ -210,19 +258,107 @@ ValueError: number must be non-negative
210258
>>>
211259
```
212260

213-
It's working.
261+
Of course, we can also raise an exception from a variable.
214262

215-
**TODO:** except Exception
263+
```py
264+
>>> oops = ValueError("number must be non-negative")
265+
>>> raise oops
266+
Traceback (most recent call last):
267+
File "<stdin>", line 1, in <module>
268+
ValueError: number must be non-negative
269+
>>>
270+
```
216271

217-
**TODO:** introduce assert here?
272+
## When should we raise exceptions?
273+
274+
Back in [the module chapter](modules.md) we learned to display error
275+
messages by printing to `sys.stderr` and then calling `sys.exit(1)`, so
276+
when should we use that and when should we raise an exception?
277+
278+
Exceptions are meant for **programmers**, so if we're writing a module
279+
that other people will import we should probably use exceptions. For
280+
other errors (for example, if the **user** of the program has done
281+
something wrong) it's usually better to use `sys.stderr` and `sys.exit`.
282+
283+
## Exception hierarchy
284+
285+
Exceptions are organized like this. I made this tree diagram with
286+
[this program](https://github.com/Akuli/classtree/) on Python 3.4. You
287+
may have more or less exceptions than I have if your Python is newer or
288+
older than mine, but they should be mostly similar.
289+
290+
Exception
291+
├── ArithmeticError
292+
│ ├── FloatingPointError
293+
│ ├── OverflowError
294+
│ └── ZeroDivisionError
295+
├── AssertionError
296+
├── AttributeError
297+
├── BufferError
298+
├── EOFError
299+
├── ImportError
300+
├── LookupError
301+
│ ├── IndexError
302+
│ └── KeyError
303+
├── MemoryError
304+
├── NameError
305+
│ └── UnboundLocalError
306+
├── OSError
307+
│ ├── BlockingIOError
308+
│ ├── ChildProcessError
309+
│ ├── ConnectionError
310+
│ │ ├── BrokenPipeError
311+
│ │ ├── ConnectionAbortedError
312+
│ │ ├── ConnectionRefusedError
313+
│ │ └── ConnectionResetError
314+
│ ├── FileExistsError
315+
│ ├── FileNotFoundError
316+
│ ├── InterruptedError
317+
│ ├── IsADirectoryError
318+
│ ├── NotADirectoryError
319+
│ ├── PermissionError
320+
│ ├── ProcessLookupError
321+
│ └── TimeoutError
322+
├── ReferenceError
323+
├── RuntimeError
324+
│ └── NotImplementedError
325+
├── StopIteration
326+
├── SyntaxError
327+
│ └── IndentationError
328+
│ └── TabError
329+
├── SystemError
330+
├── TypeError
331+
├── ValueError
332+
│ └── UnicodeError
333+
│ ├── UnicodeDecodeError
334+
│ ├── UnicodeEncodeError
335+
│ └── UnicodeTranslateError
336+
└── Warning
337+
├── BytesWarning
338+
├── DeprecationWarning
339+
├── FutureWarning
340+
├── ImportWarning
341+
├── PendingDeprecationWarning
342+
├── ResourceWarning
343+
├── RuntimeWarning
344+
├── SyntaxWarning
345+
├── UnicodeWarning
346+
└── UserWarning
347+
348+
Catching an exception also catches everything that's under it in this
349+
tree. For example, catching `OSError` catches errors that we typically
350+
get when [processing files](files.md), and catching Exception catches
351+
all errors. You don't need to remember this tree, running
352+
`help('builtins')` should display a larger tree that this is a part of.
218353

219354
## Summary
220355

221-
- Exceptions can be used just like any other variables.
356+
- Exceptions are classes and they can be used just like all other classes.
222357
- ValueError and TypeError are some of the most commonly used exceptions.
223358
- The `try` and `except` keywords can be used for attempting to do
224-
something and then doing something else if it causes an error. This
225-
is known as catching exceptions. You can use one `try` statement with
226-
multiple `except` statements.
227-
- It's also possible to raise exceptions with the `raise` keyword. This
359+
something and then doing something else if we get an error. This is
360+
known as catching exceptions.
361+
- It's possible to raise exceptions with the `raise` keyword. This
228362
is also known as throwing exceptions.
363+
- Raise exceptions if they are meant to be displayed for programmers and
364+
use `sys.stderr` and `sys.exit` otherwise.

0 commit comments

Comments
 (0)