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