diff --git a/getting_started/03_file_management.ipynb b/getting_started/03_file_management.ipynb
index 3aa9736d36d72f754dfeae3a7628d429afd2e55f..2dd68d75c84b58c1775c4740eadcfa19e97dd9ad 100644
--- a/getting_started/03_file_management.ipynb
+++ b/getting_started/03_file_management.ipynb
@@ -59,6 +59,8 @@
     " * [Re-name subject directories](#re-name-subject-directories)\n",
     " * [Re-organise a data set](#re-organise-a-data-set)\n",
     " * [Solutions](#solutions)\n",
+    "* [Appendix: Exceptions](#appendix-exceptions)\n",
+    "\n",
     "\n",
     "\n",
     "<a class=\"anchor\" id=\"managing-files-and-directories\"></a>\n",
@@ -456,9 +458,12 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> This is the first time in this series of practicals that we have defined our\n",
-    "> own function, [hooray!](https://www.youtube.com/watch?v=zQiibNVIvK4) All\n",
-    "> function definitions in Python begin with the `def` keyword:\n",
+    "> This is the first time in a while that we have defined our own function,\n",
+    "> [hooray!](https://www.youtube.com/watch?v=zQiibNVIvK4). Here's a quick\n",
+    "> refresher on how to write functions in Python, in case you have forgotten.\n",
+    ">\n",
+    "> First of all, all function definitions in Python begin with the `def`\n",
+    "> keyword:\n",
     ">\n",
     "> ```\n",
     "> def myfunction():\n",
@@ -550,10 +555,10 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> Note here that `op.split` returns both the directory and base names - it is\n",
-    "> super easy to define a Python function that returns multiple values, simply by\n",
-    "> having it return a tuple. For example, the implementation of `op.split` might\n",
-    "> look something like this:\n",
+    "> Note here that `op.split` returns both the directory and base names - remember\n",
+    "> that it is super easy to define a Python function that returns multiple values,\n",
+    "> simply by having it return a tuple. For example, the implementation of\n",
+    "> `op.split` might look something like this:\n",
     ">\n",
     ">\n",
     "> ```\n",
@@ -1031,6 +1036,367 @@
     "        formatter.get_style_defs('.highlight'),\n",
     "        highlight(code, PythonLexer(), formatter)))"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"appendix-exceptions\"></a>\n",
+    "## Appendix: Exceptions\n",
+    "\n",
+    "\n",
+    "At some point in your life, a piece of code that you write is inevitably going\n",
+    "to fail, and you are going to have to deal with it. This is particularly\n",
+    "relevant to file management tasks - many of the functions that have been\n",
+    "introduced in this practical can fail for all kinds of reasons, such as\n",
+    "incorrect permissions or ownership, lack of disk space, or a network file\n",
+    "system going down.\n",
+    "\n",
+    "\n",
+    "Any statement in Python can potentially result in an error. When a line of\n",
+    "code triggers an error, we say that it _raises_ the error (a.k.a. _throws_ in\n",
+    "other languages). When an error occurs, an `Exception` object is raised,\n",
+    "causing execution to stop at the line that caused the error:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a = [1, 2, 3]\n",
+    "a.remove(4)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The word `Exception` is used instead of `Error` because not all exceptions are\n",
+    "errors. For example, when you type CTRL+C into a running Python program, a\n",
+    "`KeyboardInterrupt` exception will be raised.\n",
+    "\n",
+    "\n",
+    "> There are many different types of exceptions in Python - a list of all the\n",
+    "> built-in ones can be found\n",
+    "> [here](https://docs.python.org/3.5/library/exceptions.html). It is also easy\n",
+    "> to define your own exceptions by creating a sub-class of `Exception` (beyond\n",
+    "> the scope of this practical).\n",
+    "\n",
+    "\n",
+    "Fortunately Python gives us the capability to _catch_ exceptions when they are\n",
+    "raised, using the `try` and `except` keywords. As an example, let's say that\n",
+    "the user asked our program to create a directory somewhere on the file system.\n",
+    "A real program would need to handle situations in which that directory cannot\n",
+    "be created - we might do it like this in Python:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "\n",
+    "dirpath = '/sbin/foo'\n",
+    "\n",
+    "try:\n",
+    "    os.mkdir(dirpath)\n",
+    "\n",
+    "except OSError as e:\n",
+    "    print('Could not create {}! Reason: {}'.format(dirpath, e))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In this example, we have put the `os.mkdir` call inside a `try:` block. Now,\n",
+    "if it raises an `Exception` of type `OSError`, that `OSError` will be _caught_\n",
+    "and passed to the `except:` block. A `try` block must always followed by an\n",
+    "`except` block (and/or a `finally` block - keep reading).\n",
+    "\n",
+    "\n",
+    "The `except OSError as e:` line means: _if any code in the `try` block raises\n",
+    "an `Exception` of type `OSError`, then catch it, assign it to a variable\n",
+    "called `e`, and pass it to the code inside the `except` block._\n",
+    "\n",
+    "\n",
+    "### Catching different types of exceptions\n",
+    "\n",
+    "\n",
+    "It is common for a piece of code to have the potential to raise different\n",
+    "types of exceptions. Python allows you to have multiple `except` blocks\n",
+    "associated with a single `try` block, so you can handle different types of\n",
+    "exceptions in different ways. For example, you might want to print a useful\n",
+    "error message so the user knows what has gone wrong:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "numerator   = '123'\n",
+    "denominator = 0\n",
+    "\n",
+    "try:\n",
+    "    numerator = float(numerator)\n",
+    "    print(numerator / denominator)\n",
+    "\n",
+    "except TypeError as e:\n",
+    "    print('Numerator and/or denominator are of the wrong type!')\n",
+    "    print('  ', e)\n",
+    "\n",
+    "except ValueError as e:\n",
+    "    print('Numerator is not a float!')\n",
+    "    print('  ', e)\n",
+    "\n",
+    "# Note that specifying a variable to refer\n",
+    "# to the Exception object is optional.\n",
+    "except ZeroDivisionError:\n",
+    "    print('Denominator is zero!')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Experiment with the above code block - try out different values for the\n",
+    "`numerator` and `denominator`, and see what happens.\n",
+    "\n",
+    "\n",
+    "You can also specify different types of exceptions in a single `except`\n",
+    "statement:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "numerator   = '123'\n",
+    "denominator = 0\n",
+    "try:\n",
+    "    numerator = float(numerator)\n",
+    "    print(numerator / denominator)\n",
+    "\n",
+    "except (TypeError, ZeroDivisionError) as e:\n",
+    "    print('Numerator and/or denominator are of the '\n",
+    "          'wrong type, or the denominator is zero!')\n",
+    "    print('  ', e)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### The catch-all approach\n",
+    "\n",
+    "\n",
+    "Instead of specifying all of the different types of exceptions that could\n",
+    "occur, it is possible to simply use a single `except` block to catch all\n",
+    "exceptions of type `Exception`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "numerator   = 'abc'\n",
+    "denominator = 1\n",
+    "\n",
+    "try:\n",
+    "    numerator = float(numerator)\n",
+    "    print(numerator / denominator)\n",
+    "\n",
+    "except Exception as e:\n",
+    "    print('Something is wrong with numerator or denominator!')\n",
+    "    print('  ', e)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "It is generally better practice to be as specific as possible when you are\n",
+    "catching exceptions, but sometimes all you care about is whether your code\n",
+    "worked or didn't, and in this case the you can simply use this catch-all\n",
+    "approach.\n",
+    "\n",
+    "\n",
+    "__Warning:__ Even though it is possible to, you should __never__ write a\n",
+    "`try`-`except` block like this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "try:\n",
+    "    # do stuff\n",
+    "    pass\n",
+    "\n",
+    "except:\n",
+    "    # handle exceptions\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "You don't actually have to specify any exception type in an `except`\n",
+    "statement.  But you should never do this!  As we have already mentioned, not\n",
+    "all exceptions are errors. The above code will catch _all_ exceptions, even\n",
+    "those which do not inherit from the standard `Exception` class. This includes\n",
+    "important exceptions such as `KeyboardInterrupt` and `SystemExit`, which\n",
+    "control important aspects of your program's behaviour.\n",
+    "\n",
+    "\n",
+    "So you should always, at the very least, specify the `Exception` type:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "try:\n",
+    "    # do stuff\n",
+    "    pass\n",
+    "\n",
+    "except Exception:\n",
+    "    # handle exceptions\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### The `finally` keyword\n",
+    "\n",
+    "\n",
+    "Sometimes, when you are performing a task, you might have some clean-up logic\n",
+    "that must be executed regardless of whether the task succeeded or failed. The\n",
+    "canonical example here is that if you open a file, you must make sure that to\n",
+    "close it when you are finished, otherwise its contents may be corrupted."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "f = open('raw_mri_data/subj_1/t1.nii', 'rb')\n",
+    "try:\n",
+    "    f.write('ho hum')\n",
+    "\n",
+    "except IOError as e:\n",
+    "    print('Error occurred!: ', e)\n",
+    "finally:\n",
+    "    print('Closing file')\n",
+    "    f.close()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "It is possible to use `try` and `finally` without an `except` block. This is\n",
+    "useful if you have some code that needs some clean-up logic, but you don't\n",
+    "actually want to catch the exception - sometimes it is better for a program\n",
+    "to crash, rather than for errors to be silently suppressed, because it can\n",
+    "be easier to figure out what went wrong:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "f = open('raw_mri_data/subj_1/t1.nii', 'rb')\n",
+    "\n",
+    "try:\n",
+    "    f.write('ho hum')\n",
+    "\n",
+    "finally:\n",
+    "    print('Closing file')\n",
+    "    f.close()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> The above was just an example - it is generally better practice to use the\n",
+    "> `with` statement when opening files.\n",
+    "\n",
+    "\n",
+    "You can read more about handling exceptions in Python\n",
+    "[here](https://docs.python.org/3.5/tutorial/errors.html).\n",
+    "\n",
+    "\n",
+    "### Raising exceptions\n",
+    "\n",
+    "\n",
+    "It is possible to generate your own exception at any point by using the\n",
+    "`raise` keyword, and passing it an `Exception` object:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "raise Exception('Kaboom!')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This can be useful if your code detects that something has gone wrong, and\n",
+    "needs to abort.\n",
+    "\n",
+    "\n",
+    "You can also raise an existing `Exception` from within an `except` block:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "try:\n",
+    "    print(1 / 0)\n",
+    "\n",
+    "except Exception:\n",
+    "    print('Some error occurred!')\n",
+    "    raise"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This can be useful if you want to print a message when an exception occurs,\n",
+    "but also allow the execption to be propagated upwards."
+   ]
   }
  ],
  "metadata": {},
diff --git a/getting_started/03_file_management.md b/getting_started/03_file_management.md
index 10b9f042967cdcc990e3b8109e5cbbf0787a5456..63b47989412b35a5a805f997ddbc5e424254f44f 100644
--- a/getting_started/03_file_management.md
+++ b/getting_started/03_file_management.md
@@ -53,6 +53,8 @@ other sections as a reference. You might miss out on some neat tricks though.
  * [Re-name subject directories](#re-name-subject-directories)
  * [Re-organise a data set](#re-organise-a-data-set)
  * [Solutions](#solutions)
+* [Appendix: Exceptions](#appendix-exceptions)
+
 
 
 <a class="anchor" id="managing-files-and-directories"></a>
@@ -799,3 +801,272 @@ def print_solution(extitle):
         formatter.get_style_defs('.highlight'),
         highlight(code, PythonLexer(), formatter)))
 ```
+
+
+<a class="anchor" id="appendix-exceptions"></a>
+## Appendix: Exceptions
+
+
+At some point in your life, a piece of code that you write is inevitably going
+to fail, and you are going to have to deal with it. This is particularly
+relevant to file management tasks - many of the functions that have been
+introduced in this practical can fail for all kinds of reasons, such as
+incorrect permissions or ownership, lack of disk space, or a network file
+system going down.
+
+
+Any statement in Python can potentially result in an error. When a line of
+code triggers an error, we say that it _raises_ the error (a.k.a. _throws_ in
+other languages). When an error occurs, an `Exception` object is raised,
+causing execution to stop at the line that caused the error:
+
+
+```
+a = [1, 2, 3]
+a.remove(4)
+```
+
+
+The word `Exception` is used instead of `Error` because not all exceptions are
+errors. For example, when you type CTRL+C into a running Python program, a
+`KeyboardInterrupt` exception will be raised.
+
+
+> There are many different types of exceptions in Python - a list of all the
+> built-in ones can be found
+> [here](https://docs.python.org/3.5/library/exceptions.html). It is also easy
+> to define your own exceptions by creating a sub-class of `Exception` (beyond
+> the scope of this practical).
+
+
+Fortunately Python gives us the capability to _catch_ exceptions when they are
+raised, using the `try` and `except` keywords. As an example, let's say that
+the user asked our program to create a directory somewhere on the file system.
+A real program would need to handle situations in which that directory cannot
+be created - we might do it like this in Python:
+
+
+```
+import os
+
+dirpath = '/sbin/foo'
+
+try:
+    os.mkdir(dirpath)
+
+except OSError as e:
+    print('Could not create {}! Reason: {}'.format(dirpath, e))
+```
+
+
+In this example, we have put the `os.mkdir` call inside a `try:` block. Now,
+if it raises an `Exception` of type `OSError`, that `OSError` will be _caught_
+and passed to the `except:` block. A `try` block must always followed by an
+`except` block (and/or a `finally` block - keep reading).
+
+
+The `except OSError as e:` line means: _if any code in the `try` block raises
+an `Exception` of type `OSError`, then catch it, assign it to a variable
+called `e`, and pass it to the code inside the `except` block._
+
+
+### Catching different types of exceptions
+
+
+It is common for a piece of code to have the potential to raise different
+types of exceptions. Python allows you to have multiple `except` blocks
+associated with a single `try` block, so you can handle different types of
+exceptions in different ways. For example, you might want to print a useful
+error message so the user knows what has gone wrong:
+
+
+```
+numerator   = '123'
+denominator = 0
+
+try:
+    numerator = float(numerator)
+    print(numerator / denominator)
+
+except TypeError as e:
+    print('Numerator and/or denominator are of the wrong type!')
+    print('  ', e)
+
+except ValueError as e:
+    print('Numerator is not a float!')
+    print('  ', e)
+
+# Note that specifying a variable to refer
+# to the Exception object is optional.
+except ZeroDivisionError:
+    print('Denominator is zero!')
+```
+
+
+Experiment with the above code block - try out different values for the
+`numerator` and `denominator`, and see what happens.
+
+
+You can also specify different types of exceptions in a single `except`
+statement:
+
+
+```
+numerator   = '123'
+denominator = 0
+try:
+    numerator = float(numerator)
+    print(numerator / denominator)
+
+except (TypeError, ZeroDivisionError) as e:
+    print('Numerator and/or denominator are of the '
+          'wrong type, or the denominator is zero!')
+    print('  ', e)
+```
+
+
+### The catch-all approach
+
+
+Instead of specifying all of the different types of exceptions that could
+occur, it is possible to simply use a single `except` block to catch all
+exceptions of type `Exception`:
+
+
+```
+numerator   = 'abc'
+denominator = 1
+
+try:
+    numerator = float(numerator)
+    print(numerator / denominator)
+
+except Exception as e:
+    print('Something is wrong with numerator or denominator!')
+    print('  ', e)
+
+```
+
+
+It is generally better practice to be as specific as possible when you are
+catching exceptions, but sometimes all you care about is whether your code
+worked or didn't, and in this case the you can simply use this catch-all
+approach.
+
+
+__Warning:__ Even though it is possible to, you should __never__ write a
+`try`-`except` block like this:
+
+
+```
+try:
+    # do stuff
+    pass
+
+except:
+    # handle exceptions
+    pass
+```
+
+
+You don't actually have to specify any exception type in an `except`
+statement.  But you should never do this!  As we have already mentioned, not
+all exceptions are errors. The above code will catch _all_ exceptions, even
+those which do not inherit from the standard `Exception` class. This includes
+important exceptions such as `KeyboardInterrupt` and `SystemExit`, which
+control important aspects of your program's behaviour.
+
+
+So you should always, at the very least, specify the `Exception` type:
+
+
+```
+try:
+    # do stuff
+    pass
+
+except Exception:
+    # handle exceptions
+    pass
+```
+
+
+### The `finally` keyword
+
+
+Sometimes, when you are performing a task, you might have some clean-up logic
+that must be executed regardless of whether the task succeeded or failed. The
+canonical example here is that if you open a file, you must make sure that to
+close it when you are finished, otherwise its contents may be corrupted.
+
+
+```
+f = open('raw_mri_data/subj_1/t1.nii', 'rb')
+try:
+    f.write('ho hum')
+
+except IOError as e:
+    print('Error occurred!: ', e)
+finally:
+    print('Closing file')
+    f.close()
+```
+
+
+It is possible to use `try` and `finally` without an `except` block. This is
+useful if you have some code that needs some clean-up logic, but you don't
+actually want to catch the exception - sometimes it is better for a program
+to crash, rather than for errors to be silently suppressed, because it can
+be easier to figure out what went wrong:
+
+
+```
+f = open('raw_mri_data/subj_1/t1.nii', 'rb')
+
+try:
+    f.write('ho hum')
+
+finally:
+    print('Closing file')
+    f.close()
+```
+
+
+> The above was just an example - it is generally better practice to use the
+> `with` statement when opening files.
+
+
+You can read more about handling exceptions in Python
+[here](https://docs.python.org/3.5/tutorial/errors.html).
+
+
+### Raising exceptions
+
+
+It is possible to generate your own exception at any point by using the
+`raise` keyword, and passing it an `Exception` object:
+
+
+```
+raise Exception('Kaboom!')
+```
+
+
+This can be useful if your code detects that something has gone wrong, and
+needs to abort.
+
+
+You can also raise an existing `Exception` from within an `except` block:
+
+
+```
+try:
+    print(1 / 0)
+
+except Exception:
+    print('Some error occurred!')
+    raise
+```
+
+This can be useful if you want to print a message when an exception occurs,
+but also allow the execption to be propagated upwards.