From 5323fc39f6f87849c267c42ef66a640329cc019c Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Tue, 6 Feb 2018 22:50:46 +0000
Subject: [PATCH] New practical on modules/packages. Needs work

---
 advanced_topics/modules_and_packages.ipynb    | 302 ++++++++++++++++++
 advanced_topics/modules_and_packages.md       | 201 ++++++++++++
 .../modules_and_packages/numfuncs.py          |   7 +
 .../modules_and_packages/sideeffects.py       |   4 +
 .../modules_and_packages/strfuncs.py          |   4 +
 5 files changed, 518 insertions(+)
 create mode 100644 advanced_topics/modules_and_packages.ipynb
 create mode 100644 advanced_topics/modules_and_packages.md
 create mode 100644 advanced_topics/modules_and_packages/numfuncs.py
 create mode 100644 advanced_topics/modules_and_packages/sideeffects.py
 create mode 100644 advanced_topics/modules_and_packages/strfuncs.py

diff --git a/advanced_topics/modules_and_packages.ipynb b/advanced_topics/modules_and_packages.ipynb
new file mode 100644
index 0000000..722d9c0
--- /dev/null
+++ b/advanced_topics/modules_and_packages.ipynb
@@ -0,0 +1,302 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Modules and packages\n",
+    "\n",
+    "\n",
+    "Python gives you a lot of flexibility in how you organise your code. If you\n",
+    "want, you can write a Python program just as you would write a Bash script.\n",
+    "You don't _have_ to use functions, classes, modules or packages if you don't\n",
+    "want to, or if the script's task does not require them.\n",
+    "\n",
+    "\n",
+    "But when your code starts to grow beyond what can reasonably be defined in a\n",
+    "single file, you will (hopefully) want to start arranging it in a more\n",
+    "understandable manner.\n",
+    "\n",
+    "\n",
+    "For this practical we have prepared a handful of example files - you can find\n",
+    "them alongside this notebook file, in a directory called\n",
+    "`modules_and_packages/`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "os.chdir('modules_and_packages')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## What is a module?\n",
+    "\n",
+    "\n",
+    "Any file ending with `.py` is considered to be a module in Python. Take a look\n",
+    "at `modules_and_packages/numfuncs.py` - either open it in your editor, or run\n",
+    "this code block:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "with open('numfuncs.py', 'rt') as f:\n",
+    "    for line in f:\n",
+    "        print(line.rstrip())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This is a perfectly valid Python module, although not a particularly useful\n",
+    "one. It contains an attribute called `PI`, and a function `add`.\n",
+    "\n",
+    "\n",
+    "## Importing modules\n",
+    "\n",
+    "\n",
+    "Before we can use our module, we must `import` it. Importing a module in\n",
+    "Python will make its contents available to the local scope.  We can import the\n",
+    "contents of `mymodule` like so:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numfuncs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This imports `numfuncs` into the local scope - everything defined in the\n",
+    "`numfuncs` module can be accessed by prefixing it with `numfuncs.`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('PI:', numfuncs.PI)\n",
+    "print(numfuncs.add(1, 50))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "There are a couple of other ways to import items from a module...\n",
+    "\n",
+    "\n",
+    "### Importing specific items from a module\n",
+    "\n",
+    "\n",
+    "If you only want to use one, or a few items from a module, you can import just\n",
+    "those items - a reference to just those items will be created in the local\n",
+    "scope:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from numfuncs import add\n",
+    "print(add(1, 3))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Importing everything from a module\n",
+    "\n",
+    "\n",
+    "It is possible to import _everything_ that is defined in a module like so:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from numfuncs import *\n",
+    "print('PI: ', PI)\n",
+    "print(add(1, 5))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "__PLEASE DON'T DO THIS!__ Because every time you do, somewhere in the world, a\n",
+    "software developer will will spontaneously stub his/her toe, and start crying.\n",
+    "Using this approach can make more complicated programs very difficult to read,\n",
+    "because it is not possible to determine the origin of the functions and\n",
+    "attributes that are being used.\n",
+    "\n",
+    "\n",
+    "And naming collisions are inevitable when importing multiple modules in this\n",
+    "way, making it very difficult for somebody else to figure out what your code\n",
+    "is doing:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from numfuncs import *\n",
+    "from strfuncs import *\n",
+    "\n",
+    "print(add(1, 5))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Instead, it is better to give modules a name when you import them.  While this\n",
+    "requires you to type more code, the benefits of doing this far outweighs the\n",
+    "hassle of typing a few extra characters - it becomes much easier to read and\n",
+    "trace through code when the functions you use are accessed through a namespace\n",
+    "for each module:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numfuncs\n",
+    "import strfuncs\n",
+    "print('number add: ', numfuncs.add(1, 2))\n",
+    "print('string add: ', strfuncs.add(1, 2))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "And Python allows you to define an _alias_ for a module when you import it,\n",
+    "so you don't necessarily need to type out the full module name each time\n",
+    "you want to access something inside:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numfuncs as nf\n",
+    "import strfuncs as sf\n",
+    "print('number add: ', nf.add(1, 2))\n",
+    "print('string add: ', sf.add(1, 2))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## What happens when I import a module?\n",
+    "\n",
+    "\n",
+    "When you `import` a module, the contents of the module file are literally\n",
+    "executed by the Python runtime, exactly the same as if you had typed its\n",
+    "contents into `ipython`. Any attributes, functions, or classes which are\n",
+    "defined in the module will be bundled up into an object that represents the\n",
+    "module, and through which you can access the module's contents.\n",
+    "\n",
+    "\n",
+    "When we typed `import numfuncs` in the examples above, the following events\n",
+    "occurred:\n",
+    "\n",
+    "\n",
+    "1. Python created a `module` object to represent the module.\n",
+    "\n",
+    "2. The `numfuncs.py` file was read and executed, and all of the items defined\n",
+    "   inside `numfuncs.py` (i.e. the `PI` attribute and the `add` function) were\n",
+    "   added to the `module` object.\n",
+    "\n",
+    "3. A local variable called `numfuncs`, pointing to the `module` object,\n",
+    "   was added to the local scope.\n",
+    "\n",
+    "\n",
+    "Because module files are literally executed on import, any statements in the\n",
+    "module file which are not encapsulated inside a class or function will be\n",
+    "executed.  As an example, take a look at the file `sideeffects.py`. Let's\n",
+    "import it and see what happens:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sideeffects"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Ok, hopefully that wasn't too much of a surprise. Something which may be less\n",
+    "intuitive, however, is that a module's contents will only be executed on the\n",
+    "_first_ time that it is imported. After the first import, Python caches the\n",
+    "module's contents (all loaded modules are accessible through\n",
+    "[`sys.modules`](https://docs.python.org/3.5/library/sys.html#sys.modules)). On\n",
+    "subsequent imports, the cached version of the module is returned:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sideeffects"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## What is a package?\n",
+    "\n",
+    "\n",
+    "\n",
+    "\n",
+    "# Useful references\n",
+    "\n",
+    "* [Modules in Python](https://docs.python.org/3.5/tutorial/modules.html)"
+   ]
+  }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/advanced_topics/modules_and_packages.md b/advanced_topics/modules_and_packages.md
new file mode 100644
index 0000000..3506a93
--- /dev/null
+++ b/advanced_topics/modules_and_packages.md
@@ -0,0 +1,201 @@
+# Modules and packages
+
+
+Python gives you a lot of flexibility in how you organise your code. If you
+want, you can write a Python program just as you would write a Bash script.
+You don't _have_ to use functions, classes, modules or packages if you don't
+want to, or if the script's task does not require them.
+
+
+But when your code starts to grow beyond what can reasonably be defined in a
+single file, you will (hopefully) want to start arranging it in a more
+understandable manner.
+
+
+For this practical we have prepared a handful of example files - you can find
+them alongside this notebook file, in a directory called
+`modules_and_packages/`.
+
+
+```
+import os
+os.chdir('modules_and_packages')
+```
+
+
+## What is a module?
+
+
+Any file ending with `.py` is considered to be a module in Python. Take a look
+at `modules_and_packages/numfuncs.py` - either open it in your editor, or run
+this code block:
+
+
+```
+with open('numfuncs.py', 'rt') as f:
+    for line in f:
+        print(line.rstrip())
+```
+
+
+This is a perfectly valid Python module, although not a particularly useful
+one. It contains an attribute called `PI`, and a function `add`.
+
+
+## Importing modules
+
+
+Before we can use our module, we must `import` it. Importing a module in
+Python will make its contents available to the local scope.  We can import the
+contents of `mymodule` like so:
+
+
+```
+import numfuncs
+```
+
+
+This imports `numfuncs` into the local scope - everything defined in the
+`numfuncs` module can be accessed by prefixing it with `numfuncs.`:
+
+
+```
+print('PI:', numfuncs.PI)
+print(numfuncs.add(1, 50))
+```
+
+
+There are a couple of other ways to import items from a module...
+
+
+### Importing specific items from a module
+
+
+If you only want to use one, or a few items from a module, you can import just
+those items - a reference to just those items will be created in the local
+scope:
+
+
+```
+from numfuncs import add
+print(add(1, 3))
+```
+
+
+### Importing everything from a module
+
+
+It is possible to import _everything_ that is defined in a module like so:
+
+
+```
+from numfuncs import *
+print('PI: ', PI)
+print(add(1, 5))
+```
+
+
+__PLEASE DON'T DO THIS!__ Because every time you do, somewhere in the world, a
+software developer will will spontaneously stub his/her toe, and start crying.
+Using this approach can make more complicated programs very difficult to read,
+because it is not possible to determine the origin of the functions and
+attributes that are being used.
+
+
+And naming collisions are inevitable when importing multiple modules in this
+way, making it very difficult for somebody else to figure out what your code
+is doing:
+
+
+```
+from numfuncs import *
+from strfuncs import *
+
+print(add(1, 5))
+```
+
+
+Instead, it is better to give modules a name when you import them.  While this
+requires you to type more code, the benefits of doing this far outweighs the
+hassle of typing a few extra characters - it becomes much easier to read and
+trace through code when the functions you use are accessed through a namespace
+for each module:
+
+
+```
+import numfuncs
+import strfuncs
+print('number add: ', numfuncs.add(1, 2))
+print('string add: ', strfuncs.add(1, 2))
+```
+
+
+And Python allows you to define an _alias_ for a module when you import it,
+so you don't necessarily need to type out the full module name each time
+you want to access something inside:
+
+
+```
+import numfuncs as nf
+import strfuncs as sf
+print('number add: ', nf.add(1, 2))
+print('string add: ', sf.add(1, 2))
+```
+
+
+## What happens when I import a module?
+
+
+When you `import` a module, the contents of the module file are literally
+executed by the Python runtime, exactly the same as if you had typed its
+contents into `ipython`. Any attributes, functions, or classes which are
+defined in the module will be bundled up into an object that represents the
+module, and through which you can access the module's contents.
+
+
+When we typed `import numfuncs` in the examples above, the following events
+occurred:
+
+
+1. Python created a `module` object to represent the module.
+
+2. The `numfuncs.py` file was read and executed, and all of the items defined
+   inside `numfuncs.py` (i.e. the `PI` attribute and the `add` function) were
+   added to the `module` object.
+
+3. A local variable called `numfuncs`, pointing to the `module` object,
+   was added to the local scope.
+
+
+Because module files are literally executed on import, any statements in the
+module file which are not encapsulated inside a class or function will be
+executed.  As an example, take a look at the file `sideeffects.py`. Let's
+import it and see what happens:
+
+
+```
+import sideeffects
+```
+
+
+Ok, hopefully that wasn't too much of a surprise. Something which may be less
+intuitive, however, is that a module's contents will only be executed on the
+_first_ time that it is imported. After the first import, Python caches the
+module's contents (all loaded modules are accessible through
+[`sys.modules`](https://docs.python.org/3.5/library/sys.html#sys.modules)). On
+subsequent imports, the cached version of the module is returned:
+
+
+```
+import sideeffects
+```
+
+
+## What is a package?
+
+
+
+
+# Useful references
+
+* [Modules in Python](https://docs.python.org/3.5/tutorial/modules.html)
\ No newline at end of file
diff --git a/advanced_topics/modules_and_packages/numfuncs.py b/advanced_topics/modules_and_packages/numfuncs.py
new file mode 100644
index 0000000..c250d8b
--- /dev/null
+++ b/advanced_topics/modules_and_packages/numfuncs.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+# See: https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/scripting/_0200.fpd/cheat3
+PI = 3.1417
+
+def add(a, b):
+    return float(a) + float(b)
diff --git a/advanced_topics/modules_and_packages/sideeffects.py b/advanced_topics/modules_and_packages/sideeffects.py
new file mode 100644
index 0000000..1ccc434
--- /dev/null
+++ b/advanced_topics/modules_and_packages/sideeffects.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+print('Hah, you\'ve imported the sideeffects module! '
+      'You\'ll never see this message again!')
diff --git a/advanced_topics/modules_and_packages/strfuncs.py b/advanced_topics/modules_and_packages/strfuncs.py
new file mode 100644
index 0000000..b9f7dd7
--- /dev/null
+++ b/advanced_topics/modules_and_packages/strfuncs.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def add(a, b):
+    return str(a) + str(b)
-- 
GitLab