@@ -18,7 +18,7 @@ kernelspec:
1818</div>
1919```
202021-# Debugging
21+# Debugging and Handling Errors
22222323```{index} single: Debugging
2424```
@@ -43,9 +43,20 @@ Hey, we all used to do that.
43434444But once you start writing larger programs you'll need a better system.
454546+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+4655Debugging tools for Python vary across platforms, IDEs and editors.
475648-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.
49605061We'll need the following imports
5162@@ -56,11 +67,7 @@ import matplotlib.pyplot as plt
5667plt.rcParams['figure.figsize'] = (10,6)
5768```
586959-## Debugging
60-61-```{index} single: Debugging
62-```
63-70+(debug_magic)=
6471### The `debug` Magic
65726673Let'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.
243250244251To exit from the debugger, use `q`.
245252246-## Other Useful Magics
253+### Other Useful Magics
247254248255In this lecture, we used the `%debug` IPython magic.
249256@@ -255,3 +262,293 @@ There are many other useful magics:
255262256263The full list of magics is [here](http://ipython.readthedocs.org/en/stable/interactive/magics.html).
257264265+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+```