{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# `kwargs`, `args`, `**kwargs`, and `*args`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In my observation, nothing in Python is more confusing than `kwargs` and `args` and how to use them. There are explanations on the web, but none ever seem comprehensive enough for me. So, here is my attempt. Like anything else, they are a feature that seem to have no point until you are stuck needing them. Then they become invaluable. \n", "\n", "*(This [notebook](https://raw.githubusercontent.com/josephcslater/iPythonExamples/master/args_n_kwargs.ipynb) can be downloaded and run within the [Jupyter notebook](jupyter.org/) environment by selecting [the link](https://raw.githubusercontent.com/josephcslater/iPythonExamples/master/args_n_kwargs.ipynb) and using ``Save Page As`` in your web browser.)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## They don't exist\n", "\n", "What? How can they not exist? Well, the Python only reserves a [limited number of keywords](https://docs.python.org/3.6/reference/lexical_analysis.html#keywords). 33 to be exact. `args` and `kwargs` aren't on the list. `kwargs` and `args` are variable names you choose. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## If they don't exist, why do I see them all over the place?\n", "\n", "They have become conventional variable names for *packed arguments* and *packed keyword arguments*. You could use any other name you'd like that is allowable in Python. These are just conventions. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is packing?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Packing is collecting a group of values into a single variable name. `*` collects values into a [tuple](https://docs.python.org/3/library/stdtypes.html?highlight=tuple#tuples) while `**` collects named values into a [dictionary](https://docs.python.org/3/tutorial/datastructures.html?highlight=dictionary#dictionaries). The distinction is somewhat irrelevant for the purpose here, outside of the apparent bundling of name and value in the dictionary. \n", "\n", "`*` and `**` are actually used outside of `args` and `kwargs`. For instance, some functions return multiple values. They can be packed by calling them with a single variable, e.g. `a = f(x)` in place of `a, b, c = f(x)`. Alternatively, if you only need the first value, you can instead call the function with `a, *_ = f(x)`, which packs the rest of the returned values into `_`, which represents nowhere. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How does `*args` work in a function?\n", "\n", "Consider a function into which we want to send a variable number of arguments." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def f(*args):\n", " print(args)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('This', 'is', 'how', 'the', 'print', 'command', 'works.')\n" ] } ], "source": [ "f('This', 'is', 'how', 'the', 'print', 'command', 'works.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, this isn't quite right. What this shows is that all of the arguments have been assembled into a tuple. They now are part of a list, and print as such. This isn't likely what we meant to do. What we want to do now is *unpack* the tuple so that the print command perceives the tuple as a group of individual arguments. " ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def g(*args): # packed into the tuple\n", " print(*args) # unpacked into individual arguments" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is how the print command works.\n" ] } ], "source": [ "g('This', 'is', 'how', 'the', 'print', 'command', 'works.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, the Python ``print`` command is much more sophisticated, but this starts to show the power of packing arguments this way. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is an example where this could be helpful?\n", "\n", "Say I want to write a function that returns the product of a number to the 3rd power, such as\n", "$$a b^3$$ However, I already have a more general function that returns $$a b^c$$" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "def power(a, b, c):\n", " return a*b**c" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power(5, 2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What I can do is create a function that calls `power`, but packs and unpacks the arguments appropriately. We know that the last argument must be the number 3. We could simply explicitly write the other arguments:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "def power3(a, b):\n", " return power(a, b, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, that's not as lazy as we would like to be. We could have instead written:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "def power3(*args):\n", " return power(*args, 3)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power3(5, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why would this be helpful? Well, there are numerous reasons. One might be error checking. It may be that `power` is from a module that is very powerful so you want to use that. However, your code may call it with arguments that are inappropriate for that library. Here I will use a `string`, but you can imagine that perhaps a complex number may not be allowed. So, we can write:" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "def power3(*args):\n", " if type(args[0]) is str:\n", " print('Oops. You sent me a string.')\n", " return\n", " else:\n", " return power(*args,3) " ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power3(5, 2)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Oops. You sent me a string.\n" ] } ], "source": [ "power3('Hello')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the string was packed into a tuple of length 1 so the code must access the 0th value. \n", "\n", "The key here is the `*`, not the name `args`. Any variable name could have been used. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## So what about `**kwargs`?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the same situation, but this time with named variables and dictionaries. This time consider writing a central finite differences operator such that\n", "$$\\frac{df}{dt}:=\\frac{f(t+\\Delta t/2)-f(t-\\Delta t/2)}{\\Delta t}$$\n", "However, if we want this to work for any function returning a numerical value, but with an unknown set of unnamed and named arguments, we can abstract by using `*args` and `**kwargs`:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def diff(f, t, dt, *args, **kwargs):\n", " return (f(t+dt/2, *args, **kwargs)\n", " -f(t-dt/2, *args, **kwargs))/dt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`f` is the name of the function we will calculate the slope of, `t` is the variable we want the slope with respect to, `dt` is a step size for that derivative. All other arguments, named or not, at passed directly through to the function `f`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define our power function again. " ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [], "source": [ "def power(t, coeff=5, exponent=3):\n", " return coeff*t**exponent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we obtain the slope when $t=2$, with a coefficient of 2 and exponent of 3." ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "24.012001999995647" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diff(power, 2, 0.001, coeff=2, exponent=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This can work with any other similarly formed function. This is a simple product of the three arguments, 2 unnamed, one named. " ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "def product(x, y, z=3):\n", " return x*y*z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We obtain the slope at $x=2$ with $y=4$ and $z=3$." ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "11.999999999999744" ] }, "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diff(product, 2, .01, 4, z=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The keys for using `**kwargs` is to remember:\n", "- `kwargs` is your variable name. You decide what it is. \n", "- `kwargs` is now a dictionary. If you want to use them to call another function, make sure to unpack it in the function call by using `**`.\n", "- You can access parts of kwargs just as you can any other dictionary. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "Hopefully this at least clarifies why they are useful. Hopefully you can even apply them- most likely when you get stuck using some external library, but needing to use it in an unintended way. Regardless, please leave your comments. I hope this helps. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" }, "toc": { "nav_menu": { "height": "163px", "width": "375px" }, "number_sections": true, "sideBar": true, "skip_h1_title": false, "toc_cell": false, "toc_position": {}, "toc_section_display": "block", "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }