{ "metadata": { "name": "", "signature": "sha256:90c5102c842575ba2f27ed5812ad4273efcda03d3e671051797be8a5c03fed0f" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[![](https://bytebucket.org/davis68/resources/raw/f7c98d2b95e961fae257707e22a58fa1a2c36bec/logos/baseline_cse_wdmk.png?token=be4cc41d4b2afe594f5b1570a3c5aad96a65f0d6)](http://cse.illinois.edu/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Debugging, Exceptions, and Bugs in Python (`pdb`)\n", "\n", "It happens to the best of us. Our code forged in the caffeine-fueled pits of night now brought forth to the day cracks and crumbles before our eyes. But what can we do about it?\n", "\n", "_Debugging_ is the process of identifying systematic errors in applications, whether from formal errors or modeling errors. An example of a formal error is an out-of-bounds error on an array; an example of modeling error is mistyping the differential equation being solved. Debugging either involves analyzing raw code, execution behavior, and output." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from __future__ import print_function, division\n", "\n", "import numpy as np\n", "import matplotlib as mpl\n", "import matplotlib.pyplot as plt\n", "import matplotlib.cm as cm\n", "%matplotlib inline" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Consider the Zen of Python:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import this" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contents\n", "- [Introduction](#intro)\n", "- [Types of Bugs](#types)\n", " - [Exceptions and Errors](#excerr)\n", " - [Tracebacks](#traceback)\n", "- [Handling bugs](#bughandle)\n", " - [Handling exceptions](#exchandle)\n", " - [Raising exceptions](#raising)\n", " - [Should I use exceptions?](#shouldi)\n", "- [Linting](#linting)\n", "- [Coding Standards](#codestd)\n", "- [Python Debugger](#pdb)\n", " - [Debugging Step-by-Step](#stepbystep)\n", " - [PDB Example](#Bessel)\n", " - [pdb in IPython](#pdbipython)\n", "- [Using a Context Manager for Elegant Expression](#with)\n", "- [Exercise](#ex1)\n", "- [References](#refs)\n", "- [Credits](#credits)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## Types of Bugs\n", "\n", "A few working definitions for our discussion today:\n", "\n", "- **Exceptions** \u2014 unusual behavior (although not necessarily unexpected behavior, particularly in Python)\n", "\n", "- **Errors** \u2014 exceptions which cause the program to be unrunnable (cannot be handled at run time)\n", "\n", "- **Tracebacks** \u2014 listing of function calls on the stack at the time the exception arises\n", "\n", "- **Bugs** \u2014 errors and exceptions, but also miswritten, ambiguous, or incorrect code _which in fact runs_ but does not advertise its miscreancy\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Exceptions & Errors\n", "\n", "Formally, an exception is an event raised to indicate that a function has failed. Most of the time, this means that the function was passed bad data, or it encountered a situation it can't handle, or just reached a known invalid result, like division by zero. (However, this may also be intentional\u2014Python causes a container to raise a `StopIteration` exception to signal that there are no items left to iterate over, for instance in a `for` loop.)\n", "\n", "Common exceptions include:\n", "- `SyntaxError` \u2014 check missing colons or parentheses\n", "- `NameError` \u2014 check for typos, function definitions\n", "- `TypeError` \u2014 check variable types (coerce if necessary)\n", "- `ValueError` \u2014 check function parameters\n", "- `IOError` \u2014 check that files exist\n", "- `IndexError` \u2014 don't reference nonexistent list elements\n", "- `KeyError` \u2014 similar to an IndexError, but for dictionaries\n", "- `ZeroDivisionError` \u2014 three guesses...\n", "- `IndentationError` \u2014 check that spaces and tabs aren't mixed\n", "- `Exception` \u2014 generic error category\n", "\n", "### Exercise\n", "- Write some snippets of code which throw the following exceptions:\n", " - `SyntaxError`\n", " - `NameError`\n", " - `TypeError`\n", " - `ValueError`\n", " - `IOError`\n", " - `IndexError`\n", " - `KeyError`\n", " - `ZeroDivisionError`" ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Tracebacks\n", "\n", "When something goes wrong in Python, the interpreter helpfully tries to show you where and why the exception occurred. Although this can be intimidating to new users, the _traceback_ is quite useful in determining the offending bit of code.\n", "\n", "Programs generally call functions on the _stack_: that is, each time a function is called, the calling function is set aside and the new function becomes the active site for the program. When this function completes, control is returned to the initial function. When this extends across many function calls, we have a deep nested structure.\n", "\n", "\n", " \n", " \n", " \n", " \n", "
\n", " \n", "%% main.py\n", "def do\\_numerics():\n", " print(sin(5.0))\n", "\n", "if \\_\\_name\\_\\_==\"\\_\\_main\\_\\_\":\n", " do_numerics()\n", " \n", " \n", " \n", "
\n", "\n", "\n", "This is what the traceback is showing us, indicating _where_ the code failed and tracing the _stack_, or nested function calls, to show you what the chain of calls was in case that helps you figure out why things went wrong." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "x = np.linspace(0, 1, 10001)\n", "y = np.cos(np.pi/x) * np.exp(-2*x)\n", "\n", "plt.plot(x[0:-1:2], y)\n", "plt.show()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Two things happened above:\n", "\n", "- The first was a warning or two, marked in Jupyter by red highlighting. These don't impact the successful completion of our code, but they can affect the quality of the results or have other externalities.\n", "\n", "- Next, a misalignment of dimensions in the vectors we desire to plot leads to an irreconciliable difficulty in the code. By my count in the current versions of NumPy and MatPlotLib, we are generating an error six layers deep in the function stack.\n", " \n", " In order to fix this problem, we need to align the vectors: either sample `y` at the same rate as `x` or don't downsample `x`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tracebacks occasionally signal problems with your installation, rather than your code. For instance, I recently had the following error arise:\n", " >>> from numpy import sin\n", " Traceback (most recent call last):\n", " File \"\", line 1, in \n", " File \"/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/__init__.py\", line 128, in \n", " from version import git_revision as __git_revision__\n", " ImportError: No module named 'version'\n", "\n", "It turns out that my distribution of Python was incorrectly using certain libraries: the `$PYTHONPATH` environment variable was set up wrong. (Incidentally, this is the sort of thing that has motivated people to counsel against using `$PYTHONPATH` at all.) When fixed, the traceback disappeared and the `import` worked properly." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try invoking a few more dramatic errors. First, how about an infinite recursion?" ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, although the Python and IPython interpreters are extremely robust, there are limits. The following, for instance, will crash the interpreter without even a traceback." ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tracebacks and exceptions are objects, and you can extract much more information if so inclined. Try this sophisticated analysis of exceptions, using `sys.exc_info`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import sys, os\n", "\n", "try:\n", " raise NotImplementedError(\"No error\")\n", "\n", "except Exception as e:\n", " exc_type, exc_obj, exc_tb = sys.exc_info()\n", " fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]\n", " print(exc_type.__name__, fname, exc_tb.tb_lineno)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## Handling bugs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Handling Exceptions (`try`/`except`/`else`/`finally`)\n", "\n", "Now what we saw above were all in fact _errors_ in that they caused execution to halt. What if we wanted to use them more intelligently\u2014that is, to diagnose and handle problems before they crash our code?\n", "\n", "This is what the `try`/`except`/`else`/`finally` workflow attempts to do. Basically, we can write a snippet of code which is susceptible to a specific error which can be handled in a `try` block, and then deal with the aftermath in the `except` block." ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`try`/`catch` error handling should encapsulate the fewest statements possible. It can also reduce code readability, and so should probably be used only where things are likely to go wrong, in your judgment: the file system, or some obtuse calculation.\n", "\n", "Basically, `try` lets you execute an error-prone segment of code, while `except` lets you handle any or all of the errors that arise. (It is better to handle less, as a general maxim, so that you don't mask other errors lurking in an operation.) An optional `finally` clause will execute in any case." ] }, { "cell_type": "code", "collapsed": false, "input": [ "filename = 'spring.data'\n", "try:\n", " data = np.genfromtxt(filename)\n", "except:\n", " print 'Unable to read file \"%s\".'%filename" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "filename = 'spring.data'\n", "try:\n", " data = np.genfromtxt(filename)\n", " print data\n", "except IOError, err:\n", " print 'Unable to read file \"%s\"; %s.'%(filename,err)\n", " # why output err? what else can go wrong?\n", "finally:\n", " print 'Done with data loading code.'" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
_**The Principle of Least Astonishment**_: The result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues.
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\">\n", "### Raising Exceptions\n", "\n", "Just as you can handle exceptions to make your code run properly, you can `raise` them as well. Generic, specific, and user-specified exceptions are all available to you." ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Should I Use Exceptions?\n", "\n", "If we are going to intelligently use exceptions to control the execution of our program, what impact will this have?\n", "\n", "- **Cons**\n", " - The use of exceptions for control structures (like `for` statements) is controversial.\n", " - In many languages, exception handling is expensive computationally and should be avoided.\n", "\n", "- **Pros**\n", " - The function can adapt to circumstances without crashing.\n", " - Code can be written for the programmer, not for the machine.\n", "\n", "The following code gives perspective on the case of relative efficiency in Python[(src)](http://www.jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "SETUP = 'counter = 0'\n", "\n", "LOOP_IF = \"\"\"\n", "counter += 1\n", "\"\"\"\n", "\n", "LOOP_EXCEPT = \"\"\"\n", "try:\n", " counter += 1\n", "except:\n", " pass\n", "\"\"\"\n", "\n", "import timeit\n", "if_time = timeit.Timer(LOOP_IF, setup=SETUP)\n", "except_time = timeit.Timer(LOOP_EXCEPT, setup=SETUP)\n", "print('using if statement: {}'.format(min(if_time.repeat(number=10 ** 7))))\n", "print('using exception: {}'.format(min(except_time.repeat(number=10 ** 7))))" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So the use of exception-handling code is not that big of a deal\u2014if `raise` makes your code easier to understand or operate, then please use it liberally. Code that is easier to read and debug is code _less likely to have bugs_, since the emergent features can be well-characterized." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Linting\n", "\n", "As I said before, you can debug by simply attempting to run your code. This, however, is very annoying. First off, the code will always stop at the first exception. This means that, if you have ten errors, you'll have to run the code ten times to find all of them. Now, imagine that this is long-running code. Imagine waiting five minutes for your code to run, only to discover it breaks because of a typo. Doesn't that sound exciting?\n", "Linting is the process of statically analyzing the code in order to catch multiple errors simultaneously (rather than dealing with them one at a time in the debugger). There are several available; I'll illustrate the use of pyflakes." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "$ pyflakes my_code.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Coding Standards\n", "In a written natural language, there are many ways to express the same idea. To make the consumption of information easier, people define style guides to enforce particularly effective ways of writing. This is even more true in coding; consistent style choices make scripts much easier to read. Just like version control, standards become absolutely essential as projects become large ($n>1$, where $n$ is the number of coders).\n", " \n", "Some programming languages have multiple competing standards, and it's easy to imagine how messy this can get. You can find strong opinions on what constitutes a tab, for instance. Luckily, Python doesn't have this issue. The official standard, PEP8, is used everywhere. Unless you plan on hiding all the code you write from the outside world, you should learn it.\n", "\n", "To help out coders, there are tools to test for compliance. The aptly named `pep8` library shows you where your code deviates from the PEP8 standard, and `autopep8` goes a step further by trying to fix all of your errors for you. These are both run from the shell. (They are not available in Canopy Basic, so you'll need to install them yourself or get the Academic license.)" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "$ pep8 my_code.py\n", "$ autopep8 my_code.py > my_new_code.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## *P*ython *D*e*B*ugger (`pdb`)\n", "\n", "Now that we've seen how errors and exceptions are raised and handled, let's handle some code interactively using [`pdb`](https://docs.python.org/3/library/pdb.html), an invaluable tool for finding subtle errors in numerical code, for instance. (If you have used the [*G*NU *D*e*B*ugger](https://www.gnu.org/software/gdb/), `gdb`, before then you will see many similarities.)\n", "\n", "Essentially, a common mode of use for `pdb` allows you to run your Python code normally until an error is reached in the interpreter. At this point, your program crashes\u2014you're probably used to this\u2014but _all of the data remain available_, and you can manipulate them to figure out what went awry and why. Another mode of interaction lets you pause program execution periodically so you can look \"under the hood\", figuring out exactly what is going on and whether the results align with expectations.\n", "\n", "(This sounds a lot like the Python interpreter, and it is. `pdb` just extends that functionality to standalone scripts as well.)\n", "\n", "First, let's just run `pdb` in a conventional program, either in IPython (which works but can output some strange tracebacks occasionally) or in a conventional Python or IPython interpreter. Another common method for using `pdb`\u2014arguably more useful\u2014is a GUI-based IDE such as [Spyder](https://pypi.python.org/pypi/spyder/2.3.1), which makes the active use of code breakpoints particularly tractable." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file pdb_vel.py\n", "import numpy as np\n", "import pdb\n", "\n", "def y_fall(t, x0, v0):\n", " a = -9.8\n", " y = a*t**2 + v0*t + x0\n", " return y\n", "\n", "v = 2520 # m/s\n", "x0 = 0 # m\n", "t = np.arange(0,300,0.1) # s\n", "\n", "pdb.set_trace()\n", "\n", "print(y_fall(t, x0, v))" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When you execute `python pdb_vel.py`, you notice that a new prompt appears, `(Pdb)`. At this prompt, you can type a series of commands. Try, for instance, `print v` or `print t[0]`. You can even directly manipulate variables: `t[0] = -1`. When you are ready to continue execution, enter `continue`, at which point the program will proceed directly (including the values of altered variables, if any)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Invocation\n", "\n", "`pdb` can be invoked in several ways. Generally speaking, you want it to either pop up _after_ an exception has been raised or run _continuously_ from a designated _breakpoint_. The following chart illustrates a few ways you can get into the `pdb` interface.\n", "\n", "![](https://bytebucket.org/davis68/python/raw/fd07c07b7b049d03024ca3a61fe143ebcc5d7ea6/lessons/img/pdb-invocation.png?token=a40dd88e3be4f02ca1aa94a26a4e21771e6faf3a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Operation\n", "\n", "This chart records the most commonly used `pdb` commands. We'll do some hands-on exercises in a few minutes so you can get a feel for what they do, and which are the most useful in a given context.\n", "\n", "![](https://bytebucket.org/davis68/python/raw/fd07c07b7b049d03024ca3a61fe143ebcc5d7ea6/lessons/img/pdb-usage.png?token=6ba619e45f3ce0cf833a1c964be24794ef6142ed)\n", "\n", "Most of these need to be seen in context to appreciate them, but I'll point out up front the most frequently used:\n", " - `n[ext]`\n", " - `s[tep]`\n", " - `r[eturn]`\n", " - `p[rint]`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Debugging a Code Step-by-Step\n", "\n", "Let's take a look at an example script which takes a positive integer as an argument and outputs all positive integer factors of that integer. Or, at least it _should_ do this. Give it a shot below.\n", "\n", "(This example is modified from one given in the [WinPDB](https://code.google.com/p/winpdb/wiki/DebuggingTutorial) documentation.)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file divisible.py\n", "from __future__ import print_function\n", "import sys\n", "\n", "def is_divisible(a, b):\n", " \"\"\"Determines if integer a is divisible by integer b.\"\"\"\n", " \n", " remainder = a % b\n", " # if there's no remainder, then a is divisible by b\n", " if not remainder:\n", " return True\n", " else:\n", " return False\n", "\n", "def find_divisors(integer):\n", " \"\"\"Find all divisors of an integer and return them as a list.\"\"\"\n", "\n", " divisors = []\n", " # we know that an integer divides itself\n", " divisors.append(integer)\n", " # we also know that the biggest divisor other than the integer itself\n", " # must be at most half the value of the integer (think about it)\n", " divisor = integer / 2\n", "\n", " while divisor >= 0:\n", " if is_divisible(integer, divisor):\n", " divisors.append(divisor)\n", " divisor =- 1\n", "\n", " return divisors\n", "\n", "if __name__ == '__main__':\n", " # do some checking of the user's input\n", " try:\n", " if len(sys.argv) == 2:\n", " # the following statement will raise a ValueError for\n", " # non-integer arguments\n", " test_integer = int(sys.argv[1])\n", " # check to make sure the integer was positive\n", " if test_integer <= 0:\n", " raise ValueError(\"integer must be positive\")\n", " elif len(sys.argv) == 1:\n", " # print the usage if there are no arguments and exit\n", " print(__doc__)\n", " sys.exit(0)\n", " else:\n", " # alert the user they did not provide the correct number of\n", " # arguments\n", " raise ValueError(\"too many arguments\")\n", " # catch the errors raised if sys.argv[1] is not a positive integer\n", " except ValueError as e:\n", " # alert the user to provide a valid argument\n", " print(\"Error: please provide one positive integer as an argument.\")\n", " sys.exit(2)\n", "\n", " divisors = find_divisors(test_integer)\n", " # print the results\n", " print(\"The divisors of %d are:\" % test_integer)\n", " for divisor in divisors:\n", " print(divisor)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "!python divisible.py 100" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Execute the code at the command prompt with `pdb` activated as follows:\n", " $ python -m pdb divisible.py 100" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Another Example\n", "\n", "Consider the following series statement for a Bessel function of the first kind,\n", "$$J_{0}(x) = \\sum_{m=0}^{\\infty} \\frac{(-1)^{m}}{m! (m+1)!} {\\left(\\frac{x}{2}\\right)}^{2m} \\text{.} $$" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file bessel.py\n", "from scipy.misc import factorial2 as fact\n", "from scipy.special import j0 # for testing error\n", "\n", "import pdb\n", "pdb.set_trace()\n", "\n", "def term(m, x):\n", " return ((-1)**m)/(fact(m)*fact(m+1)*(0.5**x)*(2*m))\n", "\n", "value = 0.5\n", "max_term = 20\n", "my_sum = 0.0\n", "for i in range(0, max_term):\n", " my_sum += term(i, value)\n", "\n", "print('series gives %f'%my_sum)\n", "print('scipy gives %f'%j0(value))\n", "print('error is %f'%(my_sum-j0(value)))" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "!python bessel.py" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### `pdb` in IPython\n", "\n", "IPython has slightly enhanced support for [debugging](http://ipython.org/ipython-doc/stable/interactive/tutorial.html#debugging) as compared to the regular Python interpreter. One convenient magic is `%debug`, which can be called immediately after failing code to analyze the traceback. (This works poorly in the IPython Notebook, but is more tractable in the command-line interpreter.)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import string\n", "\n", "greek = { 'a':u'\u03b1', 'b':u'\u03b2', 'g':u'\u03b3', 'd':u'\u03b4', 'e':u'\u03b5', 'z':u'\u03b6' }\n", "\n", "for letter in string.ascii_lowercase:\n", " print(greek[letter])" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "%debug" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Using a Context Manager for Elegant Expression (`with`)\n", "\n", "Incidentally, one very useful practice is to allow a Python _context manager_ to deal with much of the boilerplate of setting up and _always_ taking down an object (even in the case of failure). The `with` statement is used thus:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "sums = [sum(range(0,i+1)) for i in range(1,10)]\n", "\n", "with open('sums.txt', 'w') as file_out:\n", " for value in sums:\n", " print(value, file=file_out)\n", "print(\"Successfully wrote file.\")\n", "with open('sums.txt', 'r') as file_in:\n", " for line in file_in:\n", " print(line.strip(), end=',')\n", "print(\"\\nSuccessfully read file.\")" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Naturally it fails the same way, but it also closes the file without your explicit intervention." ] }, { "cell_type": "code", "collapsed": false, "input": [ "with open('nonexisting.txt', 'r') as file_in:\n", " for line in file_in:\n", " print(line.strip(), end=',')" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Thus you still have exceptions raised as you would expect, but if a file operation\u2014or anything else\u2014fails, the object is automatically closed (deallocated, whatever is in `__exit__`) by the context manager arranged by `with`. [(ref)](http://effbot.org/zone/python-with-statement.htm)\n", "\n", "The following two snippets of code are thus equivalent [(src)](https://stackoverflow.com/questions/3642080/using-python-with-statement-with-try-except-block):\n", "\n", "- Old way:\n", " try:\n", " f = open(\"file\", \"r\")\n", " try:\n", " line = f.readline()\n", " finally:\n", " f.close()\n", " except IOError:\n", " # handle error\n", "\n", "- New way:\n", " try:\n", " with open(\"file\", \"r\") as f:\n", " line = f.readline()\n", " except IOError:\n", " # handle error\n", "\n", "You can see that the first `open`s, reads, and `close`s the file; the second does the same, only much more elegantly and Pythonically." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Exercise\n", "\n", "Finite-difference models are used throughout engineering to obtain numerical solutions to differential equations. This particular system models the heat equation\n", "\n", "$$ \\frac{1}{\\alpha} \\frac{\\partial u}{\\partial t} = \\frac{\\partial^2 u}{\\partial x^2}$$\n", "\n", "given an initial condition of $u(x,t=0) = \\sin\\left(\\pi x/L\\right)$ and boundary conditions of $u(x=0,t) = 0$ and $u(x=L,t) = 0$.\n", "\n", "To approximate a derivative, the most straightforward way is to take the formal definition\n", "\n", "$$f'(x) = \\frac{f(x+h)-f(x)}{h}$$\n", "\n", "and use a small but nonzero step $h$ in your calculation.\n", "\n", "Application of this principle to the heat equation leads to a statement of the form\n", "\n", "$$ \\frac{1}{\\alpha} \\frac{u^m_i - u^{m-1}_i}{\\Delta t} = \\frac{u^{m-1}_{i-1} - 2 u^{m-1}_{i} + u^{m-1}_{i+1}}{\\Delta x^2} $$\n", "\n", "or $u^m_i = \\frac{\\alpha \\Delta t}{\\Delta x^2}u^{m-1}_{i-1} + \\left[1 - 2\\left(\\frac{\\alpha \\Delta t}{\\Delta x^2}\\right)\\right]u^{m-1}_{i} + \\frac{\\alpha \\Delta t}{\\Delta x^2}u^{m-1}_{i+1}$.\n", "\n", "This clearly yields a way to calculate subsequent time steps point-by-point from the previous time step's data.\n", "\n", "- Debug the following finite-difference code using `pdb`. Consider the following elements of the problem:\n", " - Are the sizes of `dt`, `dx`, etc., correct?\n", " - Are the ranges of the loops correct?\n", " - Are other definitions and equations correctly typed?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file fd_he.py\n", "import numpy as np\n", "\n", "# Basic parameters\n", "nt = 120\n", "nx = 25\n", "alpha = 0.1 \n", "length = 1.0\n", "tmax = 0.5\n", "\n", "# Derived parameters: mesh spacing and time step size\n", "dx = length / nx\n", "dt = tmax / (nt-1)\n", "\n", "# Create arrays to save data in process.\n", "x = np.linspace(0, length+1e-15, nx)\n", "t = np.linspace(0, tmax+1e-15, nt)\n", "u = np.zeros([nx, nt])\n", "\n", "# Set initial and boundary conditions.\n", "u[:, 0] = np.sin(np.pi*x/length)**2\n", "#boundaries are implicitly set by this initial condition\n", "\n", "# Loop through each time step.\n", "r = alpha * dt / (dx*dx)\n", "s = 1 - 2*r\n", "for n in range(1, nt):\n", " for j in range(1, nx):\n", " u[n, j] = r*u[j-1, n-1] + s*u[j, n-1] + r*u[j+1, n-1]\n", "\n", "# Output the results.\n", "np.savetxt('fd_data.txt', u)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## References\n", "\n", "- Langtangen, Hans Petter. _Python Scripting for Computational Science_, 3ed. Berlin\u2013Heidelberg: Springer\u2013Verlag, 2009.\n", "- Lugo, Michael. [On propagation of errors](http://gottwurfelt.com/2012/03/26/on-propagation-of-errors/). 26 March 2012." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## Credits\n", "\n", "Neal Davis and Lakshmi Rao developed these materials for [Computational Science and Engineering](http://cse.illinois.edu/) at the University of Illinois at Urbana\u2013Champaign. It incorporates some elements from [Software Carpentry](http://software-carpentry.org), contributed by Greg Wilson and others; and The Hacker Within, contributed by Katy Huff, Anthony Scopatz, Joshua R. Smith, and Sri Hari Krishna Narayanan.\n", "\n", "\n", "This content is available under a [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/).\n", "\n", "[![](https://bytebucket.org/davis68/resources/raw/f7c98d2b95e961fae257707e22a58fa1a2c36bec/logos/baseline_cse_wdmk.png?token=be4cc41d4b2afe594f5b1570a3c5aad96a65f0d6)](http://cse.illinois.edu/)" ] } ], "metadata": {} } ] }