From 63df73d699495324dbd69fb1b3b22fca2b3056d6 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Tue, 6 Feb 2018 13:25:53 +0000
Subject: [PATCH] Finished args/kwargs practical.

---
 .../function_inputs_and_outputs.ipynb         | 129 +++++++++++++++++-
 .../function_inputs_and_outputs.md            | 101 +++++++++++++-
 2 files changed, 222 insertions(+), 8 deletions(-)

diff --git a/advanced_topics/function_inputs_and_outputs.ipynb b/advanced_topics/function_inputs_and_outputs.ipynb
index 2306f9e..d18fc09 100644
--- a/advanced_topics/function_inputs_and_outputs.ipynb
+++ b/advanced_topics/function_inputs_and_outputs.ipynb
@@ -72,7 +72,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "You can think of the star operator as 'unpacking' the contents of the\n",
+    "You can think of the star operator as _unpacking_ the contents of the\n",
     "sequence.\n",
     "\n",
     "\n",
@@ -98,7 +98,7 @@
    "metadata": {},
    "source": [
     "Python has another operator - the double-star (`**`), which will unpack\n",
-    "keyword arguments from `dict`. For example:"
+    "keyword arguments from a `dict`. For example:"
    ]
   },
   {
@@ -149,7 +149,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "myfunc(a=1, b=2, c=3):\n",
+    "def myfunc(a=1, b=2, c=3):\n",
     "    print('First argument: ', a)\n",
     "    print('Second argument:', b)\n",
     "    print('Third argument: ', c)"
@@ -239,7 +239,128 @@
     "defined, and will persist for the duration of your program. So in this\n",
     "example, the default value for `a`, a Python `list`, gets created when\n",
     "`badfunc` is defined, and hangs around for the lifetime of the `badfunc`\n",
-    "function!"
+    "function!\n",
+    "\n",
+    "\n",
+    "## Variable numbers of arguments - `args` and `kwargs`\n",
+    "\n",
+    "\n",
+    "The `*` and `**` operators can also be used in function definitions - this\n",
+    "indicates that a function may accept a variable number of arguments.\n",
+    "\n",
+    "\n",
+    "Let's redefine `myfunc` to accept any number of positional arguments - here,\n",
+    "all positional arguments will be passed into `myfunc` as a tuple called\n",
+    "`args`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def myfunc(*args):\n",
+    "    print('myfunc({})'.format(args))\n",
+    "    print('  Number of arguments: {}'.format(len(args)))\n",
+    "    for i, arg in enumerate(args):\n",
+    "        print('  Argument {:2d}: {}'.format(i, arg))\n",
+    "\n",
+    "myfunc()\n",
+    "myfunc(1)\n",
+    "myfunc(1, 2, 3)\n",
+    "myfunc(1, 'a', [3, 4])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Similarly, we can define a function to accept any number of keyword\n",
+    "arguments. In this case, the keyword arguments will be packed into a `dict`\n",
+    "called `kwargs`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def myfunc(**kwargs):\n",
+    "    print('myfunc({})'.format(kwargs))\n",
+    "    for k, v in kwargs.items():\n",
+    "        print('  Argument {} = {}'.format(k, v))\n",
+    "\n",
+    "myfunc()\n",
+    "myfunc(a=1, b=2)\n",
+    "myfunc(a='abc', foo=123)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This is a useful technique in many circumstances. For example, if you are\n",
+    "writing a function which calls another function that takes many arguments, you\n",
+    "can use ``**kwargs`` to pass-through arguments to the second function.  As an\n",
+    "example, let's say we have functions `flirt` and `fnirt`, which respectively\n",
+    "perform linear and non-linear registration:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def flirt(infile,\n",
+    "          ref,\n",
+    "          outfile=None,\n",
+    "          init=None,\n",
+    "          omat=None,\n",
+    "          dof=12):\n",
+    "    # TODO get MJ to fill this bit in\n",
+    "    pass\n",
+    "\n",
+    "def fnirt(infile,\n",
+    "          ref,\n",
+    "          outfile=None,\n",
+    "          aff=None,\n",
+    "          interp='nn',\n",
+    "          refmask=None,\n",
+    "          minmet='lg',\n",
+    "          subsamp=4):\n",
+    "    # TODO get Jesper to fill this bit in\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We want to write our own registration function which uses the `flirt` and\n",
+    "`fnirt` functions, while also allowing the `fnirt` parameters to be\n",
+    "customised. We can use `**kwargs` to do this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def do_nonlinear_reg(infile, ref, outfile, **kwargs):\n",
+    "    \"\"\"Aligns infile to ref using non-linear registration. All keyword\n",
+    "    arguments are passed through to the fnirt function.\n",
+    "    \"\"\"\n",
+    "\n",
+    "    affmat = '/tmp/aff.mat'\n",
+    "\n",
+    "    # calculate a rough initial linear alignemnt\n",
+    "    flirt(infile, ref, omat=affmat)\n",
+    "\n",
+    "    fnirt(infile, ref, outfile, aff=affmat, **kwargs)"
    ]
   }
  ],
diff --git a/advanced_topics/function_inputs_and_outputs.md b/advanced_topics/function_inputs_and_outputs.md
index 5777da1..234cea4 100644
--- a/advanced_topics/function_inputs_and_outputs.md
+++ b/advanced_topics/function_inputs_and_outputs.md
@@ -40,7 +40,7 @@ args = [3, 4, 5]
 myfunc(*args)
 ```
 
-You can think of the star operator as 'unpacking' the contents of the
+You can think of the star operator as _unpacking_ the contents of the
 sequence.
 
 
@@ -58,7 +58,7 @@ myfunc(c=3, b=2, a=1)
 
 
 Python has another operator - the double-star (`**`), which will unpack
-keyword arguments from `dict`. For example:
+keyword arguments from a `dict`. For example:
 
 ```
 kwargs = {'a' : 4, 'b' : 5, 'c' : 6}
@@ -86,7 +86,7 @@ Function arguments can be given default values, like so:
 
 
 ```
-myfunc(a=1, b=2, c=3):
+def myfunc(a=1, b=2, c=3):
     print('First argument: ', a)
     print('Second argument:', b)
     print('Third argument: ', c)
@@ -141,4 +141,97 @@ This happens because default argument values are created when the function is
 defined, and will persist for the duration of your program. So in this
 example, the default value for `a`, a Python `list`, gets created when
 `badfunc` is defined, and hangs around for the lifetime of the `badfunc`
-function!
\ No newline at end of file
+function!
+
+
+## Variable numbers of arguments - `args` and `kwargs`
+
+
+The `*` and `**` operators can also be used in function definitions - this
+indicates that a function may accept a variable number of arguments.
+
+
+Let's redefine `myfunc` to accept any number of positional arguments - here,
+all positional arguments will be passed into `myfunc` as a tuple called
+`args`:
+
+
+```
+def myfunc(*args):
+    print('myfunc({})'.format(args))
+    print('  Number of arguments: {}'.format(len(args)))
+    for i, arg in enumerate(args):
+        print('  Argument {:2d}: {}'.format(i, arg))
+
+myfunc()
+myfunc(1)
+myfunc(1, 2, 3)
+myfunc(1, 'a', [3, 4])
+```
+
+
+Similarly, we can define a function to accept any number of keyword
+arguments. In this case, the keyword arguments will be packed into a `dict`
+called `kwargs`:
+
+
+```
+def myfunc(**kwargs):
+    print('myfunc({})'.format(kwargs))
+    for k, v in kwargs.items():
+        print('  Argument {} = {}'.format(k, v))
+
+myfunc()
+myfunc(a=1, b=2)
+myfunc(a='abc', foo=123)
+```
+
+
+This is a useful technique in many circumstances. For example, if you are
+writing a function which calls another function that takes many arguments, you
+can use ``**kwargs`` to pass-through arguments to the second function.  As an
+example, let's say we have functions `flirt` and `fnirt`, which respectively
+perform linear and non-linear registration:
+
+
+```
+def flirt(infile,
+          ref,
+          outfile=None,
+          init=None,
+          omat=None,
+          dof=12):
+    # TODO get MJ to fill this bit in
+    pass
+
+def fnirt(infile,
+          ref,
+          outfile=None,
+          aff=None,
+          interp='nn',
+          refmask=None,
+          minmet='lg',
+          subsamp=4):
+    # TODO get Jesper to fill this bit in
+    pass
+```
+
+
+We want to write our own registration function which uses the `flirt` and
+`fnirt` functions, while also allowing the `fnirt` parameters to be
+customised. We can use `**kwargs` to do this:
+
+
+```
+def do_nonlinear_reg(infile, ref, outfile, **kwargs):
+    """Aligns infile to ref using non-linear registration. All keyword
+    arguments are passed through to the fnirt function.
+    """
+
+    affmat = '/tmp/aff.mat'
+
+    # calculate a rough initial linear alignemnt
+    flirt(infile, ref, omat=affmat)
+
+    fnirt(infile, ref, outfile, aff=affmat, **kwargs)
+```
-- 
GitLab