{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Errors and Debugging" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Code development and data analysis always require a bit of trial and error, and IPython contains tools to streamline this process.\n", "This section will briefly cover some options for controlling Python's exception reporting, followed by exploring tools for debugging errors in code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Controlling Exceptions: %xmode\n", "\n", "Most of the time when a Python script fails, it will raise an exception.\n", "When the interpreter hits one of these exceptions, information about the cause of the error can be found in the *traceback*, which can be accessed from within Python.\n", "With the `%xmode` magic function, IPython allows you to control the amount of information printed when the exception is raised.\n", "Consider the following code:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def func1(a, b):\n", " return a / b\n", "\n", "def func2(x):\n", " a = x\n", " b = x - 1\n", " return func1(a, b)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mZeroDivisionError\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[0mfunc2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\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;36mfunc2\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\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;36mfunc1\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "func2(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calling `func2` results in an error, and reading the printed trace lets us see exactly what happened.\n", "In the default mode, this trace includes several lines showing the context of each step that led to the error.\n", "Using the `%xmode` magic function (short for *exception mode*), we can change what information is printed.\n", "\n", "`%xmode` takes a single argument, the mode, and there are three possibilities: `Plain`, `Context`, and `Verbose`.\n", "The default is `Context`, which gives output like that just shown.\n", "`Plain` is more compact and gives less information:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception reporting mode: Plain\n" ] } ], "source": [ "%xmode Plain" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "Traceback \u001b[0;36m(most recent call last)\u001b[0m:\n", " File \u001b[1;32m\"\"\u001b[0m, line \u001b[1;32m1\u001b[0m, in \u001b[1;35m\u001b[0m\n func2(1)\n", " File \u001b[1;32m\"\"\u001b[0m, line \u001b[1;32m7\u001b[0m, in \u001b[1;35mfunc2\u001b[0m\n return func1(a, b)\n", "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m2\u001b[0;36m, in \u001b[0;35mfunc1\u001b[0;36m\u001b[0m\n\u001b[0;31m return a / b\u001b[0m\n", "\u001b[0;31mZeroDivisionError\u001b[0m\u001b[0;31m:\u001b[0m division by zero\n" ] } ], "source": [ "func2(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Verbose` mode adds some extra information, including the arguments to any functions that are called:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception reporting mode: Verbose\n" ] } ], "source": [ "%xmode Verbose" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mZeroDivisionError\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[0mfunc2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m \u001b[0;36mglobal\u001b[0m \u001b[0;36mfunc2\u001b[0m \u001b[0;34m= \u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mfunc2\u001b[0;34m(x=1)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m \u001b[0;36mglobal\u001b[0m \u001b[0;36mfunc1\u001b[0m \u001b[0;34m= \u001b[0m\u001b[0;34m\n \u001b[0m\u001b[0;36ma\u001b[0m \u001b[0;34m= 1\u001b[0m\u001b[0;34m\n \u001b[0m\u001b[0;36mb\u001b[0m \u001b[0;34m= 0\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mfunc1\u001b[0;34m(a=1, b=0)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m \u001b[0;36ma\u001b[0m \u001b[0;34m= 1\u001b[0m\u001b[0;34m\n \u001b[0m\u001b[0;36mb\u001b[0m \u001b[0;34m= 0\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "func2(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This extra information can help you narrow in on why the exception is being raised.\n", "So why not use the `Verbose` mode all the time?\n", "As code gets complicated, this kind of traceback can get extremely long.\n", "Depending on the context, sometimes the brevity of `Plain` or `Context` mode is easier to work with." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Debugging: When Reading Tracebacks Is Not Enough\n", "\n", "The standard Python tool for interactive debugging is `pdb`, the Python debugger.\n", "This debugger lets the user step through the code line by line in order to see what might be causing a more difficult error.\n", "The IPython-enhanced version of this is `ipdb`, the IPython debugger.\n", "\n", "There are many ways to launch and use both these debuggers; we won't cover them fully here.\n", "Refer to the online documentation of these two utilities to learn more.\n", "\n", "In IPython, perhaps the most convenient interface to debugging is the `%debug` magic command.\n", "If you call it after hitting an exception, it will automatically open an interactive debugging prompt at the point of the exception.\n", "The `ipdb` prompt lets you explore the current state of the stack, explore the available variables, and even run Python commands!\n", "\n", "Let's look at the most recent exception, then do some basic tasks. We'll print the values of `a` and `b`, then type `quit` to quit the debugging session:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "> (2)func1()\n", " 1 def func1(a, b):\n", "----> 2 return a / b\n", " 3 \n", "\n", "ipdb> print(a)\n", "1\n", "ipdb> print(b)\n", "0\n", "ipdb> quit\n" ] } ], "source": [ "%debug" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The interactive debugger allows much more than this, though—we can even step up and down through the stack and explore the values of variables there:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "> (2)func1()\n", " 1 def func1(a, b):\n", "----> 2 return a / b\n", " 3 \n", "\n", "ipdb> up\n", "> (7)func2()\n", " 5 a = x\n", " 6 b = x - 1\n", "----> 7 return func1(a, b)\n", "\n", "ipdb> print(x)\n", "1\n", "ipdb> up\n", "> (1)()\n", "----> 1 func2(1)\n", "\n", "ipdb> down\n", "> (7)func2()\n", " 5 a = x\n", " 6 b = x - 1\n", "----> 7 return func1(a, b)\n", "\n", "ipdb> quit\n" ] } ], "source": [ "%debug" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows us to quickly find out not only what caused the error, but what function calls led up to the error.\n", "\n", "If you'd like the debugger to launch automatically whenever an exception is raised, you can use the `%pdb` magic function to turn on this automatic behavior:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception reporting mode: Plain\n", "Automatic pdb calling has been turned ON\n" ] }, { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "Traceback \u001b[0;36m(most recent call last)\u001b[0m:\n", " File \u001b[1;32m\"\"\u001b[0m, line \u001b[1;32m3\u001b[0m, in \u001b[1;35m\u001b[0m\n func2(1)\n", " File \u001b[1;32m\"\"\u001b[0m, line \u001b[1;32m7\u001b[0m, in \u001b[1;35mfunc2\u001b[0m\n return func1(a, b)\n", "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m2\u001b[0;36m, in \u001b[0;35mfunc1\u001b[0;36m\u001b[0m\n\u001b[0;31m return a / b\u001b[0m\n", "\u001b[0;31mZeroDivisionError\u001b[0m\u001b[0;31m:\u001b[0m division by zero\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "> (2)func1()\n", " 1 def func1(a, b):\n", "----> 2 return a / b\n", " 3 \n", "\n", "ipdb> print(b)\n", "0\n", "ipdb> quit\n" ] } ], "source": [ "%xmode Plain\n", "%pdb on\n", "func2(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, if you have a script that you'd like to run from the beginning in interactive mode, you can run it with the command `%run -d`, and use the `next` command to step through the lines of code interactively." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Partial list of debugging commands\n", "\n", "There are many more available commands for interactive debugging than I've shown here. The following table contains a description of some of the more common and useful ones:\n", "\n", "| Command | Description |\n", "|---------------|-------------------------------------------------------------|\n", "| `l(ist)` | Show the current location in the file |\n", "| `h(elp)` | Show a list of commands, or find help on a specific command |\n", "| `q(uit)` | Quit the debugger and the program |\n", "| `c(ontinue)` | Quit the debugger, continue in the program |\n", "| `n(ext)` | Go to the next step of the program |\n", "| `` | Repeat the previous command |\n", "| `p(rint)` | Print variables |\n", "| `s(tep)` | Step into a subroutine |\n", "| `r(eturn)` | Return out of a subroutine |\n", "\n", "For more information, use the `help` command in the debugger, or take a look at `ipdb`'s [online documentation](https://github.com/gotcha/ipdb)." ] } ], "metadata": { "anaconda-cloud": {}, "jupytext": { "formats": "ipynb,md" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.2" } }, "nbformat": 4, "nbformat_minor": 4 }