GitHub

@@ -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.

43434444

But 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+4655

Debugging 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.

49605061

We'll need the following imports

5162

@@ -56,11 +67,7 @@ import matplotlib.pyplot as plt

5667

plt.rcParams['figure.figsize'] = (10,6)

5768

```

586959-

## Debugging

60-61-

```{index} single: Debugging

62-

```

63-70+

(debug_magic)=

6471

### The `debug` Magic

65726673

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.

243250244251

To exit from the debugger, use `q`.

245252246-

## Other Useful Magics

253+

### Other Useful Magics

247254248255

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

249256

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

255262256263

The 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+

```

Read the original on github.com ↗