diff --git a/advanced_topics/decorators.ipynb b/advanced_topics/decorators.ipynb
index e4eed7cebfa611c9dc661127636d71070278b911..41d2bd944bcb6d9f6400c1ce5ca7ef6566d5aa30 100644
--- a/advanced_topics/decorators.ipynb
+++ b/advanced_topics/decorators.ipynb
@@ -19,6 +19,22 @@
     "These abilities mean that we can do some neat things with functions in Python.\n",
     "\n",
     "\n",
+    "* [Overview](#overview)\n",
+    "* [Decorators on methods](#decorators-on-methods)\n",
+    "* [Example - memoization](#example-memoization)\n",
+    "* [Decorators with arguments](#decorators-with-arguments)\n",
+    "* [Chaining decorators](#chaining-decorators)\n",
+    "* [Decorator classes](#decorator-classes)\n",
+    "* [Appendix: Functions are not special](#appendix-functions-are-not-special)\n",
+    "* [Appendix: Closures](#appendix-closures)\n",
+    "* [Appendix: Decorators without arguments versus decorators with arguments](#appendix-decorators-without-arguments-versus-decorators-with-arguments)\n",
+    "* [Appendix: Per-instance decorators](#appendix-per-instance-decorators)\n",
+    "* [Appendix: Preserving function metadata](#appendix-preserving-function-metadata)\n",
+    "* [Appendix: Class decorators](#appendix-class-decorators)\n",
+    "* [Useful references](#useful-references)\n",
+    "\n",
+    "\n",
+    "<a class=\"anchor\" id=\"overview\"></a>\n",
     "## Overview\n",
     "\n",
     "\n",
@@ -143,9 +159,26 @@
    "source": [
     "Here, we did the following:\n",
     "\n",
-    "1. We defined a function called `inverse`,\n",
-    "2. We passed the `inverse` function to the `timeFunc` function\n",
-    "3. We re-assigned the return value of `timeFunc` back to `inverse`.\n",
+    "\n",
+    "1. We defined a function called `inverse`:\n",
+    "\n",
+    "  > ```\n",
+    "  > def inverse(a):\n",
+    "  >     return npla.inv(a)\n",
+    "  > ```\n",
+    "\n",
+    "2. We passed the `inverse` function to the `timeFunc` function, and\n",
+    "   re-assigned the return value of `timeFunc` back to `inverse`:\n",
+    "\n",
+    "  > ```\n",
+    "  > inverse = timeFunc(inverse)\n",
+    "  > ```\n",
+    "\n",
+    "3. We called the new `inverse` function:\n",
+    "\n",
+    "  > ```\n",
+    "  > invdata = inverse(data)\n",
+    "  > ```\n",
     "\n",
     "\n",
     "So now the `inverse` variable refers to an instantiation of `wrapperFunc`,\n",
@@ -204,6 +237,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "<a class=\"anchor\" id=\"decorators-on-methods\"></a>\n",
     "## Decorators on methods\n",
     "\n",
     "\n",
@@ -279,11 +313,11 @@
     "own unique decorator.\n",
     "\n",
     "\n",
-    "If you are interested in solutions to this problem, read the next section on\n",
-    "[memoization](#example-memoization), and then take a look at the appendix on\n",
-    "[per-instance decorators](#appendix-per-instance-decorators).\n",
+    "> If you are interested in solutions to this problem, take a look at the\n",
+    "> appendix on [per-instance decorators](#appendix-per-instance-decorators).\n",
     "\n",
     "\n",
+    "<a class=\"anchor\" id=\"example-memoization\"></a>\n",
     "## Example - memoization\n",
     "\n",
     "\n",
@@ -410,6 +444,7 @@
     "> `cache` variable, refer to the [appendix on closures](#appendix-closures).\n",
     "\n",
     "\n",
+    "<a class=\"anchor\" id=\"decorators-with-arguments\"></a>\n",
     "## Decorators with arguments\n",
     "\n",
     "\n",
@@ -489,7 +524,7 @@
     "This is starting to look a little complicated - we now have _three_ layers of\n",
     "functions. This is necessary when you wish to write a decorator which accepts\n",
     "arguments (refer to the\n",
-    "[appendix](appendix-decorators-without-arguments-versus-decorators-with-arguments)\n",
+    "[appendix](#appendix-decorators-without-arguments-versus-decorators-with-arguments)\n",
     "for more details).\n",
     "\n",
     "\n",
@@ -554,6 +589,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "<a class=\"anchor\" id=\"chaining-decorators\"></a>\n",
     "## Chaining decorators\n",
     "\n",
     "\n",
@@ -599,15 +635,16 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "expensiveFunc(0.5)\n",
     "expensiveFunc(1)\n",
-    "expensiveFunc(2)\n",
-    "expensiveFunc(2)"
+    "expensiveFunc(1)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "<a class=\"anchor\" id=\"decorator-classes\"></a>\n",
     "## Decorator classes\n",
     "\n",
     "\n",
@@ -662,18 +699,19 @@
     "\n",
     "    def runTests(self):\n",
     "        for test in self.testFuncs:\n",
-    "            print('Running test {:10s} ...'.format(test.__name__), end='')\n",
+    "            print('Running test {:10s} ... '.format(test.__name__), end='')\n",
     "            try:\n",
     "                test()\n",
     "                print('passed!')\n",
     "            except Exception as e:\n",
     "                print('failed!')\n",
     "\n",
-    "# Create a test registry, and\n",
-    "# alias it to \"unitTest\" so\n",
-    "# that we can register tests\n",
-    "# with a \"@unitTest\" decorator.\n",
+    "# Create our test registry\n",
     "registry = TestRegistry()\n",
+    "\n",
+    "# Alias our registry to \"unitTest\"\n",
+    "# so that we can register tests\n",
+    "# with a \"@unitTest\" decorator.\n",
     "unitTest = registry"
    ]
   },
@@ -681,9 +719,10 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "So we've defined a class, `TestRegistry`, which will manage all of our unit\n",
-    "tests. Now, in order to \"mark\" any function as being a unit test, we just need\n",
-    "to use the `unitTest` decorator:"
+    "So we've defined a class, `TestRegistry`, and created an instance of it,\n",
+    "`registry`, which will manage all of our unit tests. Now, in order to \"mark\"\n",
+    "any function as being a unit test, we just need to use the `unitTest`\n",
+    "decorator (which is simply a reference to our `TestRegistry` instance):"
    ]
   },
   {
@@ -741,6 +780,7 @@
     "> tests were run.\n",
     "\n",
     "\n",
+    "<a class=\"anchor\" id=\"appendix-functions-are-not-special\"></a>\n",
     "## Appendix: Functions are not special\n",
     "\n",
     "\n",
@@ -911,6 +951,7 @@
     "> [preserving function metdata](#appendix-preserving-function-metadata).\n",
     "\n",
     "\n",
+    "<a class=\"anchor\" id=\"appendix-closures\"></a>\n",
     "## Appendix: Closures\n",
     "\n",
     "\n",
@@ -999,60 +1040,7 @@
     "[fishbulb](https://www.youtube.com/watch?v=CiAaEPcnlOg).\n",
     "\n",
     "\n",
-    "## Appendix: Preserving function metadata\n",
-    "\n",
-    "\n",
-    "TODO `functools.wraps`\n",
-    "\n",
-    "\n",
-    "## Appendix: per-instance decorators\n",
-    "\n",
-    "\n",
-    "Below, we have defined a class called `Multiplier`, which has a method\n",
-    "`multiply` that multiplies some number by a fixed (but changeable) constant,\n",
-    "and is memoized via the `@memoize` decorator we [defined\n",
-    "earlier](#example-memoization).\n",
-    "\n",
-    "\n",
-    "Can you spot the problem with this implementation?"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "class Multiplier(object):\n",
-    "    def __init__(self):\n",
-    "        self.__multiplier = 1\n",
-    "\n",
-    "    @property\n",
-    "    def multiplier(self):\n",
-    "        return self.multiplier\n",
-    "\n",
-    "    @multiplier.setter\n",
-    "    def multiplier(self, m):\n",
-    "        self.multiplier = m\n",
-    "\n",
-    "    @memoize\n",
-    "    def multiply(self, x):\n",
-    "        return x * self.multiplier"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "TODO Instanecify\n",
-    "\n",
-    "\n",
-    "## Appendix: class decorators\n",
-    "\n",
-    "\n",
-    "TODO\n",
-    "\n",
-    "\n",
+    "<a class=\"anchor\" id=\"appendix-decorators-without-arguments-versus-decorators-with-arguments\"></a>\n",
     "## Appendix: Decorators without arguments versus decorators with arguments\n",
     "\n",
     "\n",
@@ -1152,11 +1140,420 @@
     "> ```\n",
     "\n",
     "\n",
+    "<a class=\"anchor\" id=\"appendix-per-instance-decorators\"></a>\n",
+    "## Appendix: Per-instance decorators\n",
+    "\n",
+    "\n",
+    "In the section on [decorating methods](#decorators-on-methods), you saw\n",
+    "that when a decorator is applied to a method of a class,  that decorator\n",
+    "is invoked just once, and shared by all instances of the class. Consider this\n",
+    "example:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def decorator(func):\n",
+    "    print('Decorating {} function'.format(func.__name__))\n",
+    "    def wrapper(*args, **kwargs):\n",
+    "        print('Calling decorated function {}'.format(func.__name__))\n",
+    "        return func(*args, **kwargs)\n",
+    "    return wrapper\n",
+    "\n",
+    "class MiscMaths(object):\n",
+    "\n",
+    "    @decorator\n",
+    "    def add(self, a, b):\n",
+    "        return a + b"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Note that `decorator` was called at the time that the `MiscMaths` class was\n",
+    "defined. Now, all `MiscMaths` instances share the same `wrapper` function:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mm1 = MiscMaths()\n",
+    "mm2 = MiscMaths()\n",
+    "\n",
+    "print('1 + 2 =', mm1.add(1, 2))\n",
+    "print('3 + 4 =', mm2.add(3, 4))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This is not an issue in many cases, but it can be problematic in some. Imagine\n",
+    "if we have a decorator called `ensureNumeric`, which makes sure that arguments\n",
+    "passed to a function are numbers:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def ensureNumeric(func):\n",
+    "    def wrapper(*args):\n",
+    "        args = tuple([float(a) for a in args])\n",
+    "        return func(*args)\n",
+    "    return wrapper"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This all looks well and good - we can use it to decorate a numeric function,\n",
+    "allowing strings to be passed in as well:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@ensureNumeric\n",
+    "def mul(a, b):\n",
+    "    return a * b\n",
+    "\n",
+    "print(mul( 2,   3))\n",
+    "print(mul('5', '10'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "But what will happen when we try to decorate a method of a class?"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class MiscMaths(object):\n",
+    "\n",
+    "    @ensureNumeric\n",
+    "    def add(self, a, b):\n",
+    "        return a + b\n",
+    "\n",
+    "mm = MiscMaths()\n",
+    "print(mm.add('5', 10))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "What happened here?? Remember that the first argument passed to any instance\n",
+    "method is the instance itself (the `self` argument). Well, the `MiscMaths`\n",
+    "instance was passed to the `wrapper` function, which then tried to convert it\n",
+    "into a `float`.  So we can't actually apply the `ensureNumeric` function as a\n",
+    "decorator on a method in this way.\n",
+    "\n",
+    "\n",
+    "There are a few potential solutions here. We could modify the `ensureNumeric`\n",
+    "function, so that the `wrapper` ignores the first argument. But this would\n",
+    "mean that we couldn't use `ensureNumeric` with standalone functions.\n",
+    "\n",
+    "\n",
+    "But we _can_ manually apply the `ensureNumeric` decorator to `MiscMaths`\n",
+    "instances when they are initialised.  We can't use the nice `@ensureNumeric`\n",
+    "syntax to apply our decorators, but this is a viable approach:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class MiscMaths(object):\n",
+    "\n",
+    "    def __init__(self):\n",
+    "        self.add = ensureNumeric(self.add)\n",
+    "\n",
+    "    def add(self, a, b):\n",
+    "        return a + b\n",
+    "\n",
+    "mm = MiscMaths()\n",
+    "print(mm.add('5', 10))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Another approach is to use a second decorator, which dynamically creates the\n",
+    "real decorator when it is accessed on an instance. This requires the use of an\n",
+    "advanced Python technique called\n",
+    "[_descriptors_](https://docs.python.org/3.5/howto/descriptor.html), which is\n",
+    "beyond the scope of this practical. But if you are interested, you can see an\n",
+    "implementation of this approach\n",
+    "[here](https://git.fmrib.ox.ac.uk/fsl/fslpy/blob/1.6.8/fsl/utils/memoize.py#L249).\n",
+    "\n",
+    "\n",
+    "<a class=\"anchor\" id=\"appendix-preserving-function-metadata\"></a>\n",
+    "## Appendix: Preserving function metadata\n",
+    "\n",
+    "\n",
+    "You may have noticed that when we decorate a function, some of its properties\n",
+    "are lost. Consider this function:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def add2(a, b):\n",
+    "    \"\"\"Adds two numbers together.\"\"\"\n",
+    "    return a + b"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The `add2` function is an object which has some attributes, e.g.:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('Name: ', add2.__name__)\n",
+    "print('Help: ', add2.__doc__)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "However, when we apply a decorator to `add2`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def decorator(func):\n",
+    "    def wrapper(*args, **kwargs):\n",
+    "        \"\"\"Internal wrapper function for decorator.\"\"\"\n",
+    "        print('Calling decorated function {}'.format(func.__name__))\n",
+    "        return func(*args, **kwargs)\n",
+    "    return wrapper\n",
+    "\n",
+    "\n",
+    "@decorator\n",
+    "def add2(a, b):\n",
+    "    \"\"\"Adds two numbers together.\"\"\"\n",
+    "    return a + b"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Those attributes are lost, and instead we get the attributes of the `wrapper`\n",
+    "function:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('Name: ', add2.__name__)\n",
+    "print('Help: ', add2.__doc__)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "While this may be inconsequential in most situations, it can be quite annoying\n",
+    "in some, such as when we are automatically [generating\n",
+    "documentation](http://www.sphinx-doc.org/) for our code.\n",
+    "\n",
+    "\n",
+    "Fortunately, there is a workaround, available in the built-in\n",
+    "[`functools`](https://docs.python.org/3.5/library/functools.html#functools.wraps)\n",
+    "module:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import functools\n",
+    "\n",
+    "def decorator(func):\n",
+    "    @functools.wraps(func)\n",
+    "    def wrapper(*args, **kwargs):\n",
+    "        \"\"\"Internal wrapper function for decorator.\"\"\"\n",
+    "        print('Calling decorated function {}'.format(func.__name__))\n",
+    "        return func(*args, **kwargs)\n",
+    "    return wrapper\n",
+    "\n",
+    "@decorator\n",
+    "def add2(a, b):\n",
+    "    \"\"\"Adds two numbers together.\"\"\"\n",
+    "    return a + b"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We have applied the `@functools.wraps` decorator to our internal `wrapper`\n",
+    "function - this will essentially replace the `wrapper` function metdata with\n",
+    "the metadata from our `func` function. So our `add2` name and documentation is\n",
+    "now preserved:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('Name: ', add2.__name__)\n",
+    "print('Help: ', add2.__doc__)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"appendix-class-decorators\"></a>\n",
+    "## Appendix: Class decorators\n",
+    "\n",
+    "\n",
+    "> Not to be confused with [_decorator classes_](#decorator-classes)!\n",
+    "\n",
+    "\n",
+    "In this practical, we have shown how decorators can be applied to functions\n",
+    "and methods. But decorators can in fact also be applied to _classes_. This is\n",
+    "a fairly niche feature that you are probably not likely to need, so we will\n",
+    "only cover it briefly.\n",
+    "\n",
+    "\n",
+    "Imagine that we want all objects in our application to have a globally unique\n",
+    "(within the application) identifier. We could use a decorator which contains\n",
+    "the logic for generating unique IDs, and defines the interface that we can\n",
+    "use on an instance to obtain its ID:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import random\n",
+    "\n",
+    "allIds = set()\n",
+    "\n",
+    "def uniqueID(cls):\n",
+    "    class subclass(cls):\n",
+    "        def getUniqueID(self):\n",
+    "\n",
+    "            uid = getattr(self, '_uid', None)\n",
+    "\n",
+    "            if uid is not None:\n",
+    "                return uid\n",
+    "\n",
+    "            while uid is None or uid in set():\n",
+    "                uid = random.randint(1, 100)\n",
+    "\n",
+    "            self._uid = uid\n",
+    "            return uid\n",
+    "\n",
+    "    return subclass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now we can use the `@uniqueID` decorator on any class that we need to\n",
+    "have a unique ID:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@uniqueID\n",
+    "class Foo(object):\n",
+    "    pass\n",
+    "\n",
+    "@uniqueID\n",
+    "class Bar(object):\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "All instances of these classes will have a `getUniqueID` method:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "f1 = Foo()\n",
+    "f2 = Foo()\n",
+    "b1 = Bar()\n",
+    "b2 = Bar()\n",
+    "\n",
+    "print('f1: ', f1.getUniqueID())\n",
+    "print('f2: ', f2.getUniqueID())\n",
+    "print('b1: ', b1.getUniqueID())\n",
+    "print('b2: ', b2.getUniqueID())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"useful-references\"></a>\n",
     "## Useful references\n",
     "\n",
     "\n",
-    "* [Decorator tutorial](http://blog.thedigitalcatonline.com/blog/2015/04/23/python-decorators-metaprogramming-with-style/)\n",
-    "* [Another decorator tutorial](https://realpython.com/blog/python/primer-on-python-decorators/)\n",
+    "* [Understanding decorators in 12 easy steps](http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/)\n",
     "* [The decorators they won't tell you about](https://github.com/hchasestevens/hchasestevens.github.io/blob/master/notebooks/the-decorators-they-wont-tell-you-about.ipynb)\n",
     "* [Closures - Wikipedia][wiki-closure]\n",
     "* [Closures in Python](https://www.geeksforgeeks.org/python-closures/)\n",
diff --git a/advanced_topics/decorators.md b/advanced_topics/decorators.md
index 34e1107d04d77b633e1c4415ee86b8f96adc76ff..df8927bff086f29a4faf2efb3baeb50401f4d1ed 100644
--- a/advanced_topics/decorators.md
+++ b/advanced_topics/decorators.md
@@ -13,6 +13,22 @@ means that we can do things like:
 These abilities mean that we can do some neat things with functions in Python.
 
 
+* [Overview](#overview)
+* [Decorators on methods](#decorators-on-methods)
+* [Example - memoization](#example-memoization)
+* [Decorators with arguments](#decorators-with-arguments)
+* [Chaining decorators](#chaining-decorators)
+* [Decorator classes](#decorator-classes)
+* [Appendix: Functions are not special](#appendix-functions-are-not-special)
+* [Appendix: Closures](#appendix-closures)
+* [Appendix: Decorators without arguments versus decorators with arguments](#appendix-decorators-without-arguments-versus-decorators-with-arguments)
+* [Appendix: Per-instance decorators](#appendix-per-instance-decorators)
+* [Appendix: Preserving function metadata](#appendix-preserving-function-metadata)
+* [Appendix: Class decorators](#appendix-class-decorators)
+* [Useful references](#useful-references)
+
+
+<a class="anchor" id="overview"></a>
 ## Overview
 
 
@@ -105,9 +121,26 @@ invdata = inverse(data)
 
 Here, we did the following:
 
-1. We defined a function called `inverse`,
-2. We passed the `inverse` function to the `timeFunc` function
-3. We re-assigned the return value of `timeFunc` back to `inverse`.
+
+1. We defined a function called `inverse`:
+
+  > ```
+  > def inverse(a):
+  >     return npla.inv(a)
+  > ```
+
+2. We passed the `inverse` function to the `timeFunc` function, and
+   re-assigned the return value of `timeFunc` back to `inverse`:
+
+  > ```
+  > inverse = timeFunc(inverse)
+  > ```
+
+3. We called the new `inverse` function:
+
+  > ```
+  > invdata = inverse(data)
+  > ```
 
 
 So now the `inverse` variable refers to an instantiation of `wrapperFunc`,
@@ -150,6 +183,7 @@ invdata = inverse(data)
 ```
 
 
+<a class="anchor" id="decorators-on-methods"></a>
 ## Decorators on methods
 
 
@@ -201,11 +235,11 @@ there can be situations where you need each instance of your class to have its
 own unique decorator.
 
 
-If you are interested in solutions to this problem, read the next section on
-[memoization](#example-memoization), and then take a look at the appendix on
-[per-instance decorators](#appendix-per-instance-decorators).
+> If you are interested in solutions to this problem, take a look at the
+> appendix on [per-instance decorators](#appendix-per-instance-decorators).
 
 
+<a class="anchor" id="example-memoization"></a>
 ## Example - memoization
 
 
@@ -300,6 +334,7 @@ for i in range(10):
 > `cache` variable, refer to the [appendix on closures](#appendix-closures).
 
 
+<a class="anchor" id="decorators-with-arguments"></a>
 ## Decorators with arguments
 
 
@@ -370,7 +405,7 @@ def limitedMemoize(maxSize):
 This is starting to look a little complicated - we now have _three_ layers of
 functions. This is necessary when you wish to write a decorator which accepts
 arguments (refer to the
-[appendix](appendix-decorators-without-arguments-versus-decorators-with-arguments)
+[appendix](#appendix-decorators-without-arguments-versus-decorators-with-arguments)
 for more details).
 
 
@@ -418,6 +453,8 @@ print('The result for 10 should no longer be cached')
 fib(10)
 ```
 
+
+<a class="anchor" id="chaining-decorators"></a>
 ## Chaining decorators
 
 
@@ -450,12 +487,13 @@ Now we can see the effect of our memoization layer on performance:
 
 
 ```
+expensiveFunc(0.5)
+expensiveFunc(1)
 expensiveFunc(1)
-expensiveFunc(2)
-expensiveFunc(2)
 ```
 
 
+<a class="anchor" id="decorator-classes"></a>
 ## Decorator classes
 
 
@@ -505,24 +543,26 @@ class TestRegistry(object):
 
     def runTests(self):
         for test in self.testFuncs:
-            print('Running test {:10s} ...'.format(test.__name__), end='')
+            print('Running test {:10s} ... '.format(test.__name__), end='')
             try:
                 test()
                 print('passed!')
             except Exception as e:
                 print('failed!')
 
-# Create a test registry, and
-# alias it to "unitTest" so
-# that we can register tests
-# with a "@unitTest" decorator.
+# Create our test registry
 registry = TestRegistry()
+
+# Alias our registry to "unitTest"
+# so that we can register tests
+# with a "@unitTest" decorator.
 unitTest = registry
 ```
 
-So we've defined a class, `TestRegistry`, which will manage all of our unit
-tests. Now, in order to "mark" any function as being a unit test, we just need
-to use the `unitTest` decorator:
+So we've defined a class, `TestRegistry`, and created an instance of it,
+`registry`, which will manage all of our unit tests. Now, in order to "mark"
+any function as being a unit test, we just need to use the `unitTest`
+decorator (which is simply a reference to our `TestRegistry` instance):
 
 
 ```
@@ -563,6 +603,7 @@ registry.runTests()
 > tests were run.
 
 
+<a class="anchor" id="appendix-functions-are-not-special"></a>
 ## Appendix: Functions are not special
 
 
@@ -668,6 +709,7 @@ as we like.
 > [preserving function metdata](#appendix-preserving-function-metadata).
 
 
+<a class="anchor" id="appendix-closures"></a>
 ## Appendix: Closures
 
 
@@ -740,52 +782,7 @@ languages. So there's your answer,
 [fishbulb](https://www.youtube.com/watch?v=CiAaEPcnlOg).
 
 
-## Appendix: Preserving function metadata
-
-
-TODO `functools.wraps`
-
-
-## Appendix: per-instance decorators
-
-
-Below, we have defined a class called `Multiplier`, which has a method
-`multiply` that multiplies some number by a fixed (but changeable) constant,
-and is memoized via the `@memoize` decorator we [defined
-earlier](#example-memoization).
-
-
-Can you spot the problem with this implementation?
-
-
-```
-class Multiplier(object):
-    def __init__(self):
-        self.__multiplier = 1
-
-    @property
-    def multiplier(self):
-        return self.multiplier
-
-    @multiplier.setter
-    def multiplier(self, m):
-        self.multiplier = m
-
-    @memoize
-    def multiply(self, x):
-        return x * self.multiplier
-```
-
-
-TODO Instanecify
-
-
-## Appendix: class decorators
-
-
-TODO
-
-
+<a class="anchor" id="appendix-decorators-without-arguments-versus-decorators-with-arguments"></a>
 ## Appendix: Decorators without arguments versus decorators with arguments
 
 
@@ -868,11 +865,299 @@ def decorator(*args):
 > ```
 
 
+<a class="anchor" id="appendix-per-instance-decorators"></a>
+## Appendix: Per-instance decorators
+
+
+In the section on [decorating methods](#decorators-on-methods), you saw
+that when a decorator is applied to a method of a class,  that decorator
+is invoked just once, and shared by all instances of the class. Consider this
+example:
+
+
+```
+def decorator(func):
+    print('Decorating {} function'.format(func.__name__))
+    def wrapper(*args, **kwargs):
+        print('Calling decorated function {}'.format(func.__name__))
+        return func(*args, **kwargs)
+    return wrapper
+
+class MiscMaths(object):
+
+    @decorator
+    def add(self, a, b):
+        return a + b
+```
+
+
+Note that `decorator` was called at the time that the `MiscMaths` class was
+defined. Now, all `MiscMaths` instances share the same `wrapper` function:
+
+
+```
+mm1 = MiscMaths()
+mm2 = MiscMaths()
+
+print('1 + 2 =', mm1.add(1, 2))
+print('3 + 4 =', mm2.add(3, 4))
+```
+
+
+This is not an issue in many cases, but it can be problematic in some. Imagine
+if we have a decorator called `ensureNumeric`, which makes sure that arguments
+passed to a function are numbers:
+
+
+```
+def ensureNumeric(func):
+    def wrapper(*args):
+        args = tuple([float(a) for a in args])
+        return func(*args)
+    return wrapper
+```
+
+
+This all looks well and good - we can use it to decorate a numeric function,
+allowing strings to be passed in as well:
+
+
+```
+@ensureNumeric
+def mul(a, b):
+    return a * b
+
+print(mul( 2,   3))
+print(mul('5', '10'))
+```
+
+
+But what will happen when we try to decorate a method of a class?
+
+
+```
+class MiscMaths(object):
+
+    @ensureNumeric
+    def add(self, a, b):
+        return a + b
+
+mm = MiscMaths()
+print(mm.add('5', 10))
+```
+
+
+What happened here?? Remember that the first argument passed to any instance
+method is the instance itself (the `self` argument). Well, the `MiscMaths`
+instance was passed to the `wrapper` function, which then tried to convert it
+into a `float`.  So we can't actually apply the `ensureNumeric` function as a
+decorator on a method in this way.
+
+
+There are a few potential solutions here. We could modify the `ensureNumeric`
+function, so that the `wrapper` ignores the first argument. But this would
+mean that we couldn't use `ensureNumeric` with standalone functions.
+
+
+But we _can_ manually apply the `ensureNumeric` decorator to `MiscMaths`
+instances when they are initialised.  We can't use the nice `@ensureNumeric`
+syntax to apply our decorators, but this is a viable approach:
+
+
+```
+class MiscMaths(object):
+
+    def __init__(self):
+        self.add = ensureNumeric(self.add)
+
+    def add(self, a, b):
+        return a + b
+
+mm = MiscMaths()
+print(mm.add('5', 10))
+```
+
+
+Another approach is to use a second decorator, which dynamically creates the
+real decorator when it is accessed on an instance. This requires the use of an
+advanced Python technique called
+[_descriptors_](https://docs.python.org/3.5/howto/descriptor.html), which is
+beyond the scope of this practical. But if you are interested, you can see an
+implementation of this approach
+[here](https://git.fmrib.ox.ac.uk/fsl/fslpy/blob/1.6.8/fsl/utils/memoize.py#L249).
+
+
+<a class="anchor" id="appendix-preserving-function-metadata"></a>
+## Appendix: Preserving function metadata
+
+
+You may have noticed that when we decorate a function, some of its properties
+are lost. Consider this function:
+
+
+```
+def add2(a, b):
+    """Adds two numbers together."""
+    return a + b
+```
+
+
+The `add2` function is an object which has some attributes, e.g.:
+
+
+```
+print('Name: ', add2.__name__)
+print('Help: ', add2.__doc__)
+```
+
+
+However, when we apply a decorator to `add2`:
+
+
+```
+def decorator(func):
+    def wrapper(*args, **kwargs):
+        """Internal wrapper function for decorator."""
+        print('Calling decorated function {}'.format(func.__name__))
+        return func(*args, **kwargs)
+    return wrapper
+
+
+@decorator
+def add2(a, b):
+    """Adds two numbers together."""
+    return a + b
+```
+
+
+Those attributes are lost, and instead we get the attributes of the `wrapper`
+function:
+
+
+```
+print('Name: ', add2.__name__)
+print('Help: ', add2.__doc__)
+```
+
+
+While this may be inconsequential in most situations, it can be quite annoying
+in some, such as when we are automatically [generating
+documentation](http://www.sphinx-doc.org/) for our code.
+
+
+Fortunately, there is a workaround, available in the built-in
+[`functools`](https://docs.python.org/3.5/library/functools.html#functools.wraps)
+module:
+
+
+```
+import functools
+
+def decorator(func):
+    @functools.wraps(func)
+    def wrapper(*args, **kwargs):
+        """Internal wrapper function for decorator."""
+        print('Calling decorated function {}'.format(func.__name__))
+        return func(*args, **kwargs)
+    return wrapper
+
+@decorator
+def add2(a, b):
+    """Adds two numbers together."""
+    return a + b
+```
+
+
+We have applied the `@functools.wraps` decorator to our internal `wrapper`
+function - this will essentially replace the `wrapper` function metdata with
+the metadata from our `func` function. So our `add2` name and documentation is
+now preserved:
+
+
+```
+print('Name: ', add2.__name__)
+print('Help: ', add2.__doc__)
+```
+
+
+<a class="anchor" id="appendix-class-decorators"></a>
+## Appendix: Class decorators
+
+
+> Not to be confused with [_decorator classes_](#decorator-classes)!
+
+
+In this practical, we have shown how decorators can be applied to functions
+and methods. But decorators can in fact also be applied to _classes_. This is
+a fairly niche feature that you are probably not likely to need, so we will
+only cover it briefly.
+
+
+Imagine that we want all objects in our application to have a globally unique
+(within the application) identifier. We could use a decorator which contains
+the logic for generating unique IDs, and defines the interface that we can
+use on an instance to obtain its ID:
+
+
+```
+import random
+
+allIds = set()
+
+def uniqueID(cls):
+    class subclass(cls):
+        def getUniqueID(self):
+
+            uid = getattr(self, '_uid', None)
+
+            if uid is not None:
+                return uid
+
+            while uid is None or uid in set():
+                uid = random.randint(1, 100)
+
+            self._uid = uid
+            return uid
+
+    return subclass
+```
+
+
+Now we can use the `@uniqueID` decorator on any class that we need to
+have a unique ID:
+
+```
+@uniqueID
+class Foo(object):
+    pass
+
+@uniqueID
+class Bar(object):
+    pass
+```
+
+
+All instances of these classes will have a `getUniqueID` method:
+
+
+```
+f1 = Foo()
+f2 = Foo()
+b1 = Bar()
+b2 = Bar()
+
+print('f1: ', f1.getUniqueID())
+print('f2: ', f2.getUniqueID())
+print('b1: ', b1.getUniqueID())
+print('b2: ', b2.getUniqueID())
+```
+
+
+<a class="anchor" id="useful-references"></a>
 ## Useful references
 
 
-* [Decorator tutorial](http://blog.thedigitalcatonline.com/blog/2015/04/23/python-decorators-metaprogramming-with-style/)
-* [Another decorator tutorial](https://realpython.com/blog/python/primer-on-python-decorators/)
+* [Understanding decorators in 12 easy steps](http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/)
 * [The decorators they won't tell you about](https://github.com/hchasestevens/hchasestevens.github.io/blob/master/notebooks/the-decorators-they-wont-tell-you-about.ipynb)
 * [Closures - Wikipedia][wiki-closure]
 * [Closures in Python](https://www.geeksforgeeks.org/python-closures/)