{ "metadata": { "name": "", "signature": "sha256:0be05372ef581ab0e0848963539d7f3de1876530de06384af4be6b556a9484a2" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Python Modules and Command-line Programs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The IPython Notebook and other interactive tools are great for prototyping code and exploring data,\n", "but sooner or later one or both of the following things happen:\n", "\n", "* We develop a useful function that we want to use in other notebooks\n", "and we *know* that there has to be a better way than copying and pasting \n", "it around.\n", "\n", "* We want to use the useful function we've created in a pipeline\n", "or run via a shell script to process thousands of data files.\n", "\n", "In order to do reach those goals,\n", "we need to:\n", "\n", "1. Store our functions in a Python module\n", "2. Put a wrapper around them to make a program that works like other Unix command-line tools" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An example of a command-line script that uses our `sherlock()` function to process groups of files\n", "would look like:\n", "\n", "~~~\n", "$ python sherlock.py inflammation-*\n", "~~~\n", "\n", "or\n", "\n", "~~~\n", "$ python sherlock.py inflammation-0[6-9].csv\n", "~~~\n", "\n", "To make this work,\n", "we need to know how to handle command-line arguments in a program,\n", "and how to get at [standard input](../../gloss.html#standard-input)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Objectives\n", "\n", "* Create a Python module containing functions that can be `import`-ed into notebooks and other modules.\n", "* Use the values of command-line arguments in a program.\n", "* Read data from standard input in a program so that it can be used in a pipeline." ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Python Modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A Python module is just a text file that contains some Python functions.\n", "Once we have our functions in a module we can use the `import` statement\n", "to bring them into any number of iPython Notebooks,\n", "or into other Python modules." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So,\n", "let's put our `sherlock()` and `plot_clues()` functions into a text file called `sherlock.py`.\n", "We could:\n", "\n", "1. Open an empty file called `sherlock.py` in our favourite text editor and type the functions again\n", "2. Use copy and paste to scoop the functions from the [`04-simple_cond`](http://nbviewer.ipython.org/github/douglatornell/2014-09-25-ubc/blob/gh-pages/novice/python/04-simple_cond.ipynb) notebook into the file\n", "3. Use the iPython Notebook `%%writefile` cell magic to write the functions out of the notebook into the file\n", "\n", "We'll use `%%writefile` because it's the least work." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%writefile?" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "Type: Magic function\n", "String form: >\n", "Namespace: IPython internal\n", "File: /home/dlatornell/anaconda3/envs/swc/lib/python2.7/site-packages/IPython/core/magics/osm.py\n", "Definition: %%writefile(self, line, cell)\n", "Docstring:\n", "::\n", "\n", " %writefile [-a] filename\n", "\n", "Write the contents of the cell to a file.\n", "\n", "The file will be overwritten unless the -a (--append) flag is specified.\n", "\n", "positional arguments:\n", " filename file to write\n", "\n", "optional arguments:\n", " -a, --append Append contents of the cell to an existing file. The file will\n", " be created if it does not exist.\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To write our `sherlock()` and `plot_clues()` functions to a Python module files called `sherlock.py`\n", "put\n", "```\n", "%%writefile sherlock.py\n", "```\n", "at the top of the cell containing the functions and execute the cell.\n", "The results message will tell us that the contents of the cell have been written to a file\n", "instead of having been executed.\n", "\n", "Don't forget to delete the `%%writefile` line from the top of the cell before you save the notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also need the `analyze_stats()` function because `sherlock()` calls that too.\n", "Use\n", "```\n", "%%writefile -a sherlock.py\n", "```\n", "to append it to our `sherlock.py` module.\n", "\n", "When we're done `sherlock.py` should contain:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat sherlock.py" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "def sherlock(filenames, datalen=40):\r\n", " datamax = np.empty((len(filenames), datalen))\r\n", " for count, f in enumerate(filenames):\r\n", " datamax[count] = analyze_stats(f)[1]\r\n", " plot_clues(datamax)\r\n", "\r\n", " \r\n", "def plot_clues(datamax):\r\n", " overallmax = datamax.max(0)\r\n", " plt.plot(overallmax)\r\n", " size = datamax.shape\r\n", "\r\n", " for count in range(size[0]):\r\n", " for time in range(size[1]):\r\n", " if datamax[count, time] - overallmax[time] == -1:\r\n", " plt.plot(time, datamax[count, time], 's')\r\n", " elif datamax[count, time] < overallmax[time]:\r\n", " plt.plot(time, datamax[count, time], 'x')\r\n", " else:\r\n", " plt.plot(time, datamax[count, time], '.')\r\n", " plt.title(\r\n", " \"Overall Maximum and Deviations Away from It\\n\"\r\n", " \"dots = same as overall mean\\n\"\r\n", " \"squares = exactly 1 unit less\")\r\n", " plt.xlabel(\"Time (days)\")\r\n", " plt.ylabel(\"Inflammation (units)\")\r\n", " plt.show()def analyze_stats(filename):\r\n", " data = np.loadtxt(fname=filename, delimiter=',')\r\n", " return data.mean(0), data.max(0), data.min(0)" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have our code in a text file we'll shift to our favourite text editor to make changes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first thing we need to do is import the modules that `sherlock()` and `plot_clues()` use.\n", "\n", "In your editor,\n", "add the following lines to the top of the file,\n", "and save it.\n", "\n", "```python\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "```\n", "\n", "By convention,\n", "imports are written in alphabetical order." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also need to fix how the definition of `analyze_stats()` was appended to the file\n", "by adding a couple of empty lines and making sure that the indentation is correct.\n", "\n", "After doing those things `sherlock.py` should look like:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat sherlock.py" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "import numpy as np\r\n", "import matplotlib.pyplot as plt\r\n", "\r\n", "\r\n", "def sherlock(filenames, datalen=40):\r\n", " datamax = np.empty((len(filenames), datalen))\r\n", " for count, f in enumerate(filenames):\r\n", " datamax[count] = analyze_stats(f)[1]\r\n", " plot_clues(datamax)\r\n", "\r\n", "\r\n", "def plot_clues(datamax):\r\n", " overallmax = datamax.max(0)\r\n", " plt.plot(overallmax)\r\n", " size = datamax.shape\r\n", "\r\n", " for count in range(size[0]):\r\n", " for time in range(size[1]):\r\n", " if datamax[count, time] - overallmax[time] == -1:\r\n", " plt.plot(time, datamax[count, time], 's')\r\n", " elif datamax[count, time] < overallmax[time]:\r\n", " plt.plot(time, datamax[count, time], 'x')\r\n", " else:\r\n", " plt.plot(time, datamax[count, time], '.')\r\n", " plt.title(\r\n", " \"Overall Maximum and Deviations Away from It\\n\"\r\n", " \"dots = same as overall mean\\n\"\r\n", " \"squares = exactly 1 unit less\")\r\n", " plt.xlabel(\"Time (days)\")\r\n", " plt.ylabel(\"Inflammation (units)\")\r\n", " plt.show()\r\n", "\r\n", "\r\n", "def analyze_stats(filename):\r\n", " data = np.loadtxt(fname=filename, delimiter=',')\r\n", " return data.mean(0), data.max(0), data.min(0)\r\n" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now,\n", "we can import our `sherlock()` function and use it just in this notebook\n", "just like we used it in the previous notebook where we developed it:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%matplotlib inline\n", "\n", "import glob\n", "\n", "import sherlock\n", "\n", "sherlock.sherlock(glob.glob('inflammation-*.csv'))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "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\nNQAAAABJRU5ErkJggg==\n", "text": [ "" ] } ], "prompt_number": 12 }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Docstrings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's add some docstrings to our module and its functions to tell our future selves\n", "and others what the code does.\n", "Recall the the convention for docstrings is to enclose them in sets of triple double quotes: `\"\"\"`.\n", "The module docstring goes at the top of the module\n", "(before the imports),\n", "and the function docstrings go at the tops of the functions\n", "(just after the `def` statements).\n", "\n", "Once we have some docstring in our module we can reload it into the notebook and use\n", "the help features to check them out." ] }, { "cell_type": "code", "collapsed": false, "input": [ "reload(sherlock)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 10, "text": [ "" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "> #### Reload?\n", ">\n", "> The import statement only works once during a Python session.\n", "> After that,\n", "> Python will not reread your code,\n", "> no matter how many times you import it.\n", "> This means that if you import your code from a file,\n", "> then change and save the file,\n", "> those changes won't be reflected in Python,\n", "> even if you `import` your module again.\n", "> To explicitly force Python to reread your module after a change,\n", "> use the `reload()` function." ] }, { "cell_type": "code", "collapsed": false, "input": [ "sherlock?" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 17 }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "Type: module\n", "String form: \n", "File: /home/dlatornell/swc/2014-09-25-ubc/novice/python/sherlock.py\n", "Docstring:\n", "Investigate the suspicious similarities in the inflammation data files.\n", "\n", "Plot a graph that shows how little the per-dataset maximum inflammation\n", "deviates from the overal maximum value.\n", "```" ] }, { "cell_type": "code", "collapsed": false, "input": [ "sherlock.sherlock?" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 18 }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "Type: function\n", "String form: \n", "File: /home/dlatornell/swc/2014-09-25-ubc/novice/python/sherlock.py\n", "Definition: sherlock.sherlock(filenames, datalen=40)\n", "Docstring:\n", "Detect the suspicious lack of differences in the maximum\n", "inflamation values.\n", "```" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Command-Line Arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use your editor to create a new file called `argv-list.py` that contains the lines:\n", "```python\n", "import sys\n", "print 'sys.argv is', sys.argv\n", "```\n", "That strange name `argv` stands for \"argument values\".\n", "Whenever Python runs a program,\n", "it takes all of the values given on the command line\n", "and puts them in the list `sys.argv`\n", "so that the program can determine what they were." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we use Python to run this program at the command-line:\n", "```\n", "$ python argv-list.py\n", "```\n", "The output should look something like:\n", "```\n", "sys.argv is ['argv-list.py']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and if we run it from a different directory,\n", "we get:\n", "```\n", "$ cd ..\n", "$ python hbridge/argv-list.py\n", "sys.argv is ['hbridge/argv-list.py']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The only thing in the list is the path to our script,\n", "which is always `sys.argv[0]`.\n", "If we run it with a few arguments,\n", "however:\n", "```\n", "$ python hbridge/argv-list.py one two three\n", "sys.argv is ['hbridge/argv-list.py', 'one', 'two', 'three']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "then Python adds each of those arguments to the list." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that knowledge we can add a feature to our `sherlock.py` module to\n", "enable us to run `sherlock` from the command-line and pass in the list\n", "of files that we want it to operate on via shell wildcard characters." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In your text editor,\n", "add\n", "```python\n", "import sys\n", "```\n", "to the collection of import statements at the top of the `sherlock.py` file,\n", "and add a new function definition:\n", "```python\n", "def main():\n", " \"\"\"Command-line interface.\n", " \"\"\"\n", " script = sys.argv[0]\n", " files = sys.argv[1:]\n", " for fn in files:\n", " print fn\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `main()` function is the function that we're going to call when the module\n", "is executed by Python from the command-line.\n", "It's a Python convention to call it `main()`,\n", "but you could call it anything else if you wanted to.\n", "At this point `main()` is just a \"stub\" for testing -\n", "it doesn't call `sherlock()` yet,\n", "but we'll get there..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need to call the `main()` when we do:\n", "```bash\n", "$ python sherlock.py\n", "```\n", "but we also want to still be able to use:\n", "```python\n", "import sherlock\n", "```\n", "in our code as we did a few minutes ago.\n", "There is a detail that we need to take care of for things to work both ways." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When Python imports a module it executes the code in the module.\n", "If we just put a call to `main()` in `sherlock.py` it will be executed whenever\n", "we do `import sherlock`,\n", "perhaps with unpleasant (or unexpected) results.\n", "That's what's known as an \"import side-effect\" and it's bad form in Python programming.\n", "Fortunately,\n", "Python provides a way to solve this problem.\n", "There is a special variable,\n", "`__name__`,\n", "which is set when Python reads a module.\n", "If Python is reading the module because it has been run from the command-line,\n", "`__name__` is set to `\"__main__\"`,\n", "but if the module is being read because it is being imported,\n", "`__name__` is set to the name of the module." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use that to ensure that our `main()` function is only executed\n", "when the module is run at the command-line by adding these 2 lines of code\n", "to the end of `sherlock.py`:\n", "```python\n", "if __name__ == '__main__':\n", " main()\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With those changes in place,\n", "here's what `sherlock.py` looks like now:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat sherlock.py" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\"\"\"Investigate the suspicious similarities in the inflammation data files.\r\n", "\r\n", "Plot a graph that shows how little the per-dataset maximum inflammation\r\n", "deviates from the overal maximum value.\r\n", "\"\"\"\r\n", "import sys\r\n", "\r\n", "import numpy as np\r\n", "import matplotlib.pyplot as plt\r\n", "\r\n", "\r\n", "def main():\r\n", " \"\"\"Command-line interface.\r\n", " \"\"\"\r\n", " script = sys.argv[0]\r\n", " files = sys.argv[1:]\r\n", " sherlock(files)\r\n", "\r\n", "\r\n", "def sherlock(filenames, datalen=40):\r\n", " \"\"\"Detect the suspicious lack of differences in the maximum\r\n", " inflamation values.\r\n", " \"\"\"\r\n", " datamax = np.empty((len(filenames), datalen))\r\n", " for count, f in enumerate(filenames):\r\n", " datamax[count] = analyze_stats(f)[1]\r\n", " count += 1\r\n", " plot_clues(datamax)\r\n", "\r\n", "\r\n", "def plot_clues(datamax):\r\n", " overallmax = datamax.max(0)\r\n", " plt.plot(overallmax)\r\n", " size = datamax.shape\r\n", "\r\n", " for count in range(size[0]):\r\n", " for time in range(size[1]):\r\n", " if datamax[count, time] - overallmax[time] == -1:\r\n", " plt.plot(time, datamax[count, time], 's')\r\n", " elif datamax[count, time] < overallmax[time]:\r\n", " plt.plot(time, datamax[count, time], 'x')\r\n", " else:\r\n", " plt.plot(time, datamax[count, time], '.')\r\n", " plt.title(\r\n", " \"Overall Maximum and Deviations Away from It\\n\"\r\n", " \"dots = same as overall mean\\n\"\r\n", " \"squares = exactly 1 unit less\")\r\n", " plt.xlabel(\"Time (days)\")\r\n", " plt.ylabel(\"Inflammation (units)\")\r\n", " plt.show()\r\n", "\r\n", "\r\n", "def analyze_stats(filename):\r\n", " data = np.loadtxt(fname=filename, delimiter=',')\r\n", " return data.mean(0), data.max(0), data.min(0)\r\n", "\r\n", "\r\n", "if __name__ == '__main__':\r\n", " main()\r\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's test it!\n", "```bash\n", "$ python sherlock.py inflammation-01.csv \n", "inflammation-01.csv\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "$ python sherlock.py inflammation-*.csv \n", "inflammation-01.csv\n", "inflammation-02.csv\n", "inflammation-03.csv\n", "inflammation-04.csv\n", "inflammation-05.csv\n", "inflammation-06.csv\n", "inflammation-07.csv\n", "inflammation-08.csv\n", "inflammation-09.csv\n", "inflammation-10.csv\n", "inflammation-11.csv\n", "inflammation-12.csv\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "$ python sherlock.py inflammation-0[6-9].csv \n", "inflammation-06.csv\n", "inflammation-07.csv\n", "inflammation-08.csv\n", "inflammation-09.csv\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected,\n", "`sherlock.py` is printing out the list of files that we are asking for\n", "with the shell wildcard characters.\n", "So, it's just a small change now to call the `sherlock()` function on that list:\n", "```python\n", "def main():\n", " \"\"\"Command-line interface.\n", " \"\"\"\n", " script = sys.argv[0]\n", " files = sys.argv[1:]\n", " sherlock(files)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now,\n", "when we run\n", "```bash\n", "$ python sherlock.py inflammation-*.csv\n", "```\n", "a Matplotlib graph window pops open:\n", "\n", "![matplotlib graph window](img/matplotlib-graph-window.png)\n", "\n", "It has tool buttons to let you pan and zoom in the image,\n", "and also to save the image to a file." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In summary:\n", "\n", "* We started with some functions that we developed in a\n", "[`notebook`](http://nbviewer.ipython.org/github/douglatornell/2014-09-25-ubc/blob/gh-pages/novice/python/04-simple_cond.ipynb)\n", "but that we decided we wanted to use elsewhere.\n", "* We captured those functions in a Python module (`sherlock.py`) with the help of the IPython Notebook ``%%writefile`` cell magic.\n", "* We modified the module in a text editor to add imports and docstrings.\n", "* We demonstrated that we could import from our `sherlock.py` module,\n", "and use the help features of IPython to read our docstrings.\n", "* We modified `sherlock.py` so that it could be used as a command-line script as well as an import.\n", "* We used the `sherlock()` function to produce graphs both in-line in an IPython Notebook,\n", "and in a separate graph window.\n", "\n", "This is an example of code re-use and the DRY\n", "(Don't Repeat Yourself) principle.\n", "We developed `sherlock()` and its associated functions in one context and made them useful\n", "in other ways without having to copy/paste them and maintain multiple versions." ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Connecting to the Pipeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Although it doesn't make much sense in the context of `sherlock`,\n", "you can probably imagine wanting to write a Python script that you could\n", "use as part of a shell pipeline:\n", "```bash\n", "$ python myscript.py < inflammation-*.csv | head -20\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Python provides a special \"file\" called `sys.stdin`,\n", "which is automatically connected to the program's standard input.\n", "We don't have to open it \u2014\n", "Python and the operating system take care of that when the program starts up \u2014\n", "but we can do almost anything with it that we could do to a regular file." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use that in a `main()` function like this:\n", "```python\n", "def main():\n", " script = sys.argv[0]\n", " filenames = sys.argv[1:]\n", " if not filenames:\n", " process(sys.stdin)\n", " else:\n", " process(filenames)\n", "```" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Argparse" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Creating command-line scripts with multiple arguments,\n", "option flags,\n", "built-in help,\n", "etc. is a common enough task that people have written Python libraries to\n", "take care of most of the tedious details.\n", "The Python standard library has a module named [argparse](http://docs.python.org/dev/library/argparse.html)\n", "that does that.\n", "When you want to get serious about creating Python command-line scripts\n", "you should go to Tshepang Lekhonkhobe's\n", "[Argparse tutorial](http://docs.python.org/dev/howto/argparse.html)\n", "that is part of Python's Official Documentation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Key Points\n", "\n", "* A Python module is a text file containing Python code.\n", "* `%%writefile` lets you output code developed in a Notebook to a Python module.\n", "* The `sys` library connects a Python program to the system it is running on.\n", "* The list `sys.argv` contains the command-line arguments that a program was run with.\n", "* `if __name__ == \"__main__\":` provides a shield that prevents code in a module\n", "from being executed when the module is imported,\n", "but allows it to be run from the command-line.\n", "* The \"file\" `sys.stdin` connects to a program's standard input." ] } ], "metadata": {} } ] }