Skip to content

Commit ba924f5

Browse files
authored
Lecture Review: Major Restructuring of Advanced Python Programming Section, Task 2-3 (#218)
* update * restructure namespace * adding namespace to summary * add link for garbage collection * oop exercise * add a label for boolean data type * fix typos * change title * restructure debugging section * update oop_intro * add visualisations * add images for each line * update debugging chapter * restructure intro to visualisation * fix typos * fix dash * fix indent * update image * update f string * integrate comments * show output * fix typo * clarify comment * update comments
1 parent 76ab162 commit ba924f5

File tree

8 files changed

+930
-777
lines changed

8 files changed

+930
-777
lines changed
198 KB
Loading
225 KB
Loading
297 KB
Loading
249 KB
Loading

lectures/debugging.md

Lines changed: 305 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ kernelspec:
1818
</div>
1919
```
2020

21-
# Debugging
21+
# Debugging and Handling Errors
2222

2323
```{index} single: Debugging
2424
```
@@ -43,9 +43,20 @@ Hey, we all used to do that.
4343

4444
But once you start writing larger programs you'll need a better system.
4545

46+
You may also want to handle potential errors in your code as they occur.
47+
48+
In this lecture, we will discuss how to debug our programs and improve error handling.
49+
50+
## Debugging
51+
52+
```{index} single: Debugging
53+
```
54+
4655
Debugging tools for Python vary across platforms, IDEs and editors.
4756

48-
Here we'll focus on Jupyter and leave you to explore other settings.
57+
For example, a [visual debugger](https://jupyterlab.readthedocs.io/en/stable/user/debugger.html) is available in JupyterLab.
58+
59+
Here we'll focus on Jupyter Notebook and leave you to explore other settings.
4960

5061
We'll need the following imports
5162

@@ -56,11 +67,7 @@ import matplotlib.pyplot as plt
5667
plt.rcParams['figure.figsize'] = (10,6)
5768
```
5869

59-
## Debugging
60-
61-
```{index} single: Debugging
62-
```
63-
70+
(debug_magic)=
6471
### The `debug` Magic
6572

6673
Let's consider a simple (and rather contrived) example
@@ -243,7 +250,7 @@ Then we printed the value of `x` to see what was happening with that variable.
243250

244251
To exit from the debugger, use `q`.
245252

246-
## Other Useful Magics
253+
### Other Useful Magics
247254

248255
In this lecture, we used the `%debug` IPython magic.
249256

@@ -255,3 +262,293 @@ There are many other useful magics:
255262

256263
The full list of magics is [here](http://ipython.readthedocs.org/en/stable/interactive/magics.html).
257264

265+
266+
## Handling Errors
267+
268+
```{index} single: Python; Handling Errors
269+
```
270+
271+
Sometimes it's possible to anticipate bugs and errors as we're writing code.
272+
273+
For example, the unbiased sample variance of sample $y_1, \ldots, y_n$
274+
is defined as
275+
276+
$$
277+
s^2 := \frac{1}{n-1} \sum_{i=1}^n (y_i - \bar y)^2
278+
\qquad \bar y = \text{ sample mean}
279+
$$
280+
281+
This can be calculated in NumPy using `np.var`.
282+
283+
But if you were writing a function to handle such a calculation, you might
284+
anticipate a divide-by-zero error when the sample size is one.
285+
286+
One possible action is to do nothing --- the program will just crash, and spit out an error message.
287+
288+
But sometimes it's worth writing your code in a way that anticipates and deals with runtime errors that you think might arise.
289+
290+
Why?
291+
292+
* Because the debugging information provided by the interpreter is often less useful than what can be provided by a well written error message.
293+
* Because errors that cause execution to stop interrupt workflows.
294+
* Because it reduces confidence in your code on the part of your users (if you are writing for others).
295+
296+
297+
In this section, we'll discuss different types of errors in Python and techniques to handle potential errors in our programs.
298+
299+
### Errors in Python
300+
301+
We have seen `AttributeError` and `NameError` in {any}`our previous examples <debug_magic>`.
302+
303+
In Python, there are two types of errors -- syntax errors and exceptions.
304+
305+
```{index} single: Python; Exceptions
306+
```
307+
308+
Here's an example of a common error type
309+
310+
```{code-cell} python3
311+
---
312+
tags: [raises-exception]
313+
---
314+
def f:
315+
```
316+
317+
Since illegal syntax cannot be executed, a syntax error terminates execution of the program.
318+
319+
Here's a different kind of error, unrelated to syntax
320+
321+
```{code-cell} python3
322+
---
323+
tags: [raises-exception]
324+
---
325+
1 / 0
326+
```
327+
328+
Here's another
329+
330+
```{code-cell} python3
331+
---
332+
tags: [raises-exception]
333+
---
334+
x1 = y1
335+
```
336+
337+
And another
338+
339+
```{code-cell} python3
340+
---
341+
tags: [raises-exception]
342+
---
343+
'foo' + 6
344+
```
345+
346+
And another
347+
348+
```{code-cell} python3
349+
---
350+
tags: [raises-exception]
351+
---
352+
X = []
353+
x = X[0]
354+
```
355+
356+
On each occasion, the interpreter informs us of the error type
357+
358+
* `NameError`, `TypeError`, `IndexError`, `ZeroDivisionError`, etc.
359+
360+
In Python, these errors are called *exceptions*.
361+
362+
### Assertions
363+
364+
```{index} single: Python; Assertions
365+
```
366+
367+
Sometimes errors can be avoided by checking whether your program runs as expected.
368+
369+
A relatively easy way to handle checks is with the `assert` keyword.
370+
371+
For example, pretend for a moment that the `np.var` function doesn't
372+
exist and we need to write our own
373+
374+
```{code-cell} python3
375+
def var(y):
376+
n = len(y)
377+
assert n > 1, 'Sample size must be greater than one.'
378+
return np.sum((y - y.mean())**2) / float(n-1)
379+
```
380+
381+
If we run this with an array of length one, the program will terminate and
382+
print our error message
383+
384+
```{code-cell} python3
385+
---
386+
tags: [raises-exception]
387+
---
388+
var([1])
389+
```
390+
391+
The advantage is that we can
392+
393+
* fail early, as soon as we know there will be a problem
394+
* supply specific information on why a program is failing
395+
396+
### Handling Errors During Runtime
397+
398+
```{index} single: Python; Runtime Errors
399+
```
400+
401+
The approach used above is a bit limited, because it always leads to
402+
termination.
403+
404+
Sometimes we can handle errors more gracefully, by treating special cases.
405+
406+
Let's look at how this is done.
407+
408+
#### Catching Exceptions
409+
410+
We can catch and deal with exceptions using `try` -- `except` blocks.
411+
412+
Here's a simple example
413+
414+
```{code-cell} python3
415+
def f(x):
416+
try:
417+
return 1.0 / x
418+
except ZeroDivisionError:
419+
print('Error: division by zero. Returned None')
420+
return None
421+
```
422+
423+
When we call `f` we get the following output
424+
425+
```{code-cell} python3
426+
f(2)
427+
```
428+
429+
```{code-cell} python3
430+
f(0)
431+
```
432+
433+
```{code-cell} python3
434+
f(0.0)
435+
```
436+
437+
The error is caught and execution of the program is not terminated.
438+
439+
Note that other error types are not caught.
440+
441+
If we are worried the user might pass in a string, we can catch that error too
442+
443+
```{code-cell} python3
444+
def f(x):
445+
try:
446+
return 1.0 / x
447+
except ZeroDivisionError:
448+
print('Error: Division by zero. Returned None')
449+
except TypeError:
450+
print(f'Error: x cannot be of type {type(x)}. Returned None')
451+
return None
452+
```
453+
454+
Here's what happens
455+
456+
```{code-cell} python3
457+
f(2)
458+
```
459+
460+
```{code-cell} python3
461+
f(0)
462+
```
463+
464+
```{code-cell} python3
465+
f('foo')
466+
```
467+
468+
If we feel lazy we can catch these errors together
469+
470+
```{code-cell} python3
471+
def f(x):
472+
try:
473+
return 1.0 / x
474+
except:
475+
print(f'Error. An issue has occurred with x = {x} of type: {type(x)}')
476+
return None
477+
```
478+
479+
Here's what happens
480+
481+
```{code-cell} python3
482+
f(2)
483+
```
484+
485+
```{code-cell} python3
486+
f(0)
487+
```
488+
489+
```{code-cell} python3
490+
f('foo')
491+
```
492+
493+
In general it's better to be specific.
494+
495+
496+
## Exercises
497+
498+
```{exercise-start}
499+
:label: debug_ex1
500+
```
501+
502+
Suppose we have a text file `numbers.txt` containing the following lines
503+
504+
```{code-block} none
505+
:class: no-execute
506+
507+
prices
508+
3
509+
8
510+
511+
7
512+
21
513+
```
514+
515+
Using `try` -- `except`, write a program to read in the contents of the file and sum the numbers, ignoring lines without numbers.
516+
517+
You can use the `open()` function we learnt {any}`before<iterators>` to open `numbers.txt`.
518+
```{exercise-end}
519+
```
520+
521+
522+
```{solution-start} debug_ex1
523+
:class: dropdown
524+
```
525+
526+
Let's save the data first
527+
528+
```{code-cell} python3
529+
%%file numbers.txt
530+
prices
531+
3
532+
8
533+
534+
7
535+
21
536+
```
537+
538+
```{code-cell} python3
539+
f = open('numbers.txt')
540+
541+
total = 0.0
542+
for line in f:
543+
try:
544+
total += float(line)
545+
except ValueError:
546+
pass
547+
548+
f.close()
549+
550+
print(total)
551+
```
552+
553+
```{solution-end}
554+
```

0 commit comments

Comments
 (0)