@@ -18,7 +18,7 @@ kernelspec:
18
18
</div>
19
19
```
20
20
21
- # Debugging
21
+ # Debugging and Handling Errors
22
22
23
23
``` {index} single: Debugging
24
24
```
@@ -43,9 +43,20 @@ Hey, we all used to do that.
43
43
44
44
But once you start writing larger programs you'll need a better system.
45
45
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
+
46
55
Debugging tools for Python vary across platforms, IDEs and editors.
47
56
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.
49
60
50
61
We'll need the following imports
51
62
@@ -56,11 +67,7 @@ import matplotlib.pyplot as plt
56
67
plt.rcParams['figure.figsize'] = (10,6)
57
68
```
58
69
59
- ## Debugging
60
-
61
- ``` {index} single: Debugging
62
- ```
63
-
70
+ (debug_magic)=
64
71
### The ` debug ` Magic
65
72
66
73
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.
243
250
244
251
To exit from the debugger, use ` q ` .
245
252
246
- ## Other Useful Magics
253
+ ### Other Useful Magics
247
254
248
255
In this lecture, we used the ` %debug ` IPython magic.
249
256
@@ -255,3 +262,293 @@ There are many other useful magics:
255
262
256
263
The full list of magics is [ here] ( http://ipython.readthedocs.org/en/stable/interactive/magics.html ) .
257
264
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