{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "![header](header2.png)\n", "\n", "# Handling exceptions, and debugging\n", "\n", "## Exceptions\n", "\n", "So far, errors have been the bane of our lives. We pass the wrong kind of argument to a function, we make an accidental division by 0, or we try to access a list element that doesn't exist. The whole program crashes, and we're frustrated trying to find out what went wrong.\n", "\n", "Now we can turn this situation on its head. Python lets us anticipate places where errors might occur, and allows us to tell the program what to do if it encounters an error, instead of coming grinding to a halt. In other words, we can put our errors to work for us.\n", "\n", "\"Great!\" you might be thinking. \"I'll just ignore all errors!\" Not so fast, of course. The point of error messages is they tell us where our program is going wrong and prevents more serious problems happening further down the line. We want to be informed when an unexpected error occurs as that means there's something wrong with our program. What exception handling allows us to do is manage the errors that we do expect to happen from time to time. This means our exception handling should be carefully tailored to the circumstances." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### raise\n", "\n", "The raise keyword is followed by the name of an exception (a procedure that occurs when an error occurs), and causes that kind of exception to occur. We can also provide an error message to go with the error" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "This is the wrong type of thing", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"This is the wrong type of thing\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: This is the wrong type of thing" ] } ], "source": [ "raise TypeError(\"This is the wrong type of thing\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This can be used along with if statements to only raise the exception under specific circumstances. Suppose I have a function that is supposed to operate on lists:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def acts_on_a_list(a_list):\n", " for x in a_list:\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now before calling that function, I might write:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "letters variable should be a list", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mletters\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m'S'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'a'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'm'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mletters\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"letters variable should be a list\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0macts_on_a_list\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mletters\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: letters variable should be a list" ] } ], "source": [ "letters = ('S', 'a', 'm')\n", "if not isinstance(letters, list):\n", " raise TypeError(\"letters variable should be a list\")\n", "acts_on_a_list(letters)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you see, I have anticipated that my function might receive the wrong kind of input at this stage, and given the user an error message to explain what they are doing wrong.\n", "\n", "## try, except, finally\n", "\n", "Astute reads will have noticed that actually, my function acts_on_a_list() doesn't require a list at all. It could take as its input anything sequential, such as a tuple, a dictionary, a range object, or maybe things we haven't even considered. I don't want to have to create if... raise statements for every possible kind of input my function could take, right?\n", "\n", "This is where try comes in, and the mantra that \"It's easier to ask forgiveness than permission\". In short, try does what it says on the tin: it tries to do something! The difference is that we can provide further instructions in case what it is trying to do goes wrong. This would be more appropriate:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "S\n", "a\n", "m\n" ] } ], "source": [ "try:\n", " acts_on_a_list(letters)\n", "except TypeError:\n", " print(\"acts_on_a_list was not provided with a sequence!\")\n", " " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "acts_on_a_list was not provided with a sequence!\n" ] } ], "source": [ "try:\n", " acts_on_a_list(42)\n", "except TypeError:\n", " print(\"acts_on_a_list was not provided with a sequence!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So this one didn't actually cause the error message. Instead, the exception was \"caught\" before the error message occurred, and a new bit of code was executed to say what happens next. This could be, for instance, performing an alternative version of the action:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "acts_on_a_list was not provided with a sequence!\n", "E\n", "x\n", "a\n", "m\n", "p\n", "l\n", "e\n" ] } ], "source": [ "try:\n", " acts_on_a_list(42)\n", "except TypeError:\n", " print(\"acts_on_a_list was not provided with a sequence!\")\n", " acts_on_a_list(\"Example\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice, we have precisely specified what kind of error we wish to catch: other kinds of errors will still occur in the usual way." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "ename": "IndexError", "evalue": "list index out of range", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0macts_on_a_list\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"HiPy\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Sam\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"acts_on_a_list was not provided with a sequence!\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0macts_on_a_list\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Example\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mIndexError\u001b[0m: list index out of range" ] } ], "source": [ "try:\n", " acts_on_a_list([\"HiPy\", \"Sam\"][3])\n", "except TypeError:\n", " print(\"acts_on_a_list was not provided with a sequence!\")\n", " acts_on_a_list(\"Example\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a good thing -- remember, we usually only want to catch the expected errors, and still be alerted properly if something truly unexpected happens. It's possible to use pass to continue as if nothing happened" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Look, nothing happened\n" ] } ], "source": [ "try:\n", " acts_on_a_list(3.14)\n", "except TypeError:\n", " pass\n", "print(\"Look, nothing happened\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And it seems like nothing happened. If we use except without providing the name of an exception, all exceptions will be handled in the same way (or ignored, if this is followed by pass). This is occasionally useful, but only if you know what you're doing -- specific, targeted errors are preferred. Here's a more practical little example of try, to add corresponding elements :" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def add_list_elements(listA, listB):\n", " longest_list = max([listA, listB], key=len)\n", " listC = [x for x in longest_list]\n", " for i, value in enumerate(listC):\n", " try:\n", " listC[i] = listA[i] + listB[i]\n", " except IndexError:\n", " pass\n", " return listC" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we start by creating a copy of the longest list. Then for each index of the new list, we attempt to replace that list entry with the sum of the corresponding entries from the original of the two lists. If this fails due to an index error, which it eventually will if the lists are different sizes, we do nothing -- we just leave it as the same entry as the longer list:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[15, 25, 3, 123, 4]\n" ] } ], "source": [ "A = [5, 5, 3, 123, 4]\n", "B = [10, 20]\n", "print(add_list_elements(A,B))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can respond to different exceptions by giving them in sequence, as in:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "try:\n", " something()\n", "except Exception1:\n", " do_this()\n", "except Exception2:\n", " do_that()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "try...except can be followed with an else clause, which occurs only if there was no exception.\n", "\n", "A final part can be added after a try...except block. This is finally. This is a piece of code that will run regardless of success, failure, caught or uncaught exceptions. It will run no matter what, even if the rest of your program comes crashing down.\n", "\n", "This is not unfamiliar -- it is precisely what with open() does when you open a file: it has a procedure for closing the file regardless of what happens while the file is open.\n", "\n", "As an example of a good time to raise an exception, recall the polynomial class we made in tutorial 10. Virtually nothing will work if the argument provided is not a sequence of numbers, so we might want to add a check:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Polynomial(list):\n", " def __init__(self, *coeffs):\n", " from collections.abc import Sequence\n", " from numbers import Number\n", " if isinstance(coeffs[0], Sequence):\n", " coeffs = coeffs[0]\n", " if not isinstance(coeffs, Sequence):\n", " raise TypeError(\"Argument should be a sequence of numbers\")\n", " for x in coeffs:\n", " if not isinstance(x, Number):\n", " raise TypeError(\"Argument should be a sequence of numbers\")\n", " \n", " coeffs = list(coeffs)\n", " list.__init__(coeffs)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Argument should be a sequence of numbers", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mPolynomial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'a'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, *coeffs)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcoeffs\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Argument should be a sequence of numbers\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0mcoeffs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcoeffs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: Argument should be a sequence of numbers" ] } ], "source": [ "Polynomial([3, 'a', 5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Debugging\n", "\n", "While we're on the subject of errors, we should talk a little bit about how to root out those bugs. One way that can be quite fruitful is simply heavy use of the print() function, telling Python to print various bits of informations as your program is running so you can spot where something has gone wrong. You can then \"comment out\" or delete the print() functions when you are done with them (and you probably should, since while printing might not seem like much it can slow a program down a surprising amount!).\n", "\n", "Python provides a module that allows you to walk through your program step by step to see how it is working. Just import the module pdb, and you can get started.\n", "\n", "Firstly, it's good not to have to debug your program from the start, but to choose a problematic part of the program to interrogate. Just add pdb.set_trace() before the bit of code you wish to examine.\n", "\n", "Now when you run your code, you can interact with it as it is executing using simple commands. The first to know is that q is quit.\n", "\n", "To advance through your program, you have two main options, step and next, which can be abbreviated as s or n. The difference is s will go \"inside\" a function on a line, but n will simply evaluate the function and move on to the next line. This distinction is not really clear from the names, so I like to imagine that s stands for \"sub-routine\", which is another word for \"function\" in computer programming. To skip ahead to the end of a function you are currently \"inside\", type r, which stands for return.\n", "\n", "So, great, we can move through the lines of our program step by step. But what does this really tell us? Well, the great thing is, we can actually run any line of code or print any variable while we're inside. To run a line of code (for instance, to change a variable manually to see what happens), just type that line in, or prefix the line with a ! if there is any risk of ambiguity. To print a variable inside the debugger, you can just type p variable_name, to save you typing print() every time. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "### EXAMPLE VIDEO TO GO HERE" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 }