@@ -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
4444But 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+
4655Debugging 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
5061We'll need the following imports
5162
@@ -56,11 +67,7 @@ import matplotlib.pyplot as plt
5667plt.rcParams['figure.figsize'] = (10,6)
5768```
5869
59- ## Debugging
60-
61- ``` {index} single: Debugging
62- ```
63-
70+ (debug_magic)=
6471### The ` debug ` Magic
6572
6673Let'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
244251To exit from the debugger, use ` q ` .
245252
246- ## Other Useful Magics
253+ ### Other Useful Magics
247254
248255In this lecture, we used the ` %debug ` IPython magic.
249256
@@ -255,3 +262,293 @@ There are many other useful magics:
255262
256263The 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