Advanced Argument Passing

This is a very, very nifty Python feature – it really lets you write dynamic programs.

Keyword arguments

When defining a function, you can specify only what you need – in any order

In [151]: def fun(x=0, y=0, z=0):
        print(x,y,z)
   .....:
In [152]: fun(1,2,3)
1 2 3
In [153]: fun(1, z=3)
1 0 3
In [154]: fun(z=3, y=2)
0 2 3

A Common Idiom:

Can set defaults to variables

In [156]: y = 4
In [157]: def fun(x=y):
    print("x is:", x)
   .....:
In [158]: fun()
x is: 4

Defaults are evaluated when the function is defined

In [156]: y = 4
In [157]: def fun(x=y):
    print("x is:", x)
   .....:
In [158]: fun()
x is: 4
In [159]: y = 6
In [160]: fun()
x is: 4

This is a very important point.

Function arguments in variables

When a function is called, its arguments are really just:

  • a tuple (positional arguments)

  • a dict (keyword arguments)

def f(x, y, w=0, h=0):
    print("position: {}, {} -- shape: {}, {}".format(x, y, w, h))

position = (3,4)
size = {'h': 10, 'w': 20}

>>> f(*position, **size)
position: 3, 4 -- shape: 20, 10

Function parameters in variables

You can also pull the parameters out in the function as a tuple and a dict:

def f(*args, **kwargs):
    print("the positional arguments are:", args)
    print("the keyword arguments are:", kwargs)

In [389]: f(2, 3, this=5, that=7)
the positional arguments are: (2, 3)
the keyword arguments are: {'this': 5, 'that': 7}

This can be very powerful…

Passing a dict to str.format()

Now that you know that keyword args are really a dict, you know how this nifty trick works:

The string format() method takes keyword arguments:

In [24]: "My name is {first} {last}".format(last="Barker", first="Chris")
Out[24]: 'My name is Chris Barker'

Build a dict of the keys and values:

In [25]: d = {"last":"Barker", "first":"Chris"}

And pass to format() with **

In [26]: "My name is {first} {last}".format(**d)
Out[26]: 'My name is Chris Barker'

Kinda handy for the dict lab, eh?

This:

print("{} is from {}, and he likes "
      "{} cake, {} fruit, {} salad, "
      "and {} pasta.".format(food_prefs["name"],
                             food_prefs["city"],
                             food_prefs["cake"],
                             food_prefs["fruit"],
                             food_prefs["salad"],
                             food_prefs["pasta"]))

Becomes:

print("{name} is from {city}, and he likes "
      "{cake} cake, {fruit} fruit, {salad} salad, "
      "and {pasta} pasta.".format(**food_prefs))

Note that this is particularity useful when the same value is used in multiple places in the format string.

Keyword Only Arguments

The usual function signature looks something like:

def fun (pos1, pos2, key1='this', key2='that'):
    print(pos1, pos2, key1, key2)

In this case, we have two positional parameters and two keyword parameters.

But all four can be passed as either positional or keyword arguments:

In [21]: fun(1,2,3,4)
1 2 3 4

In [22]: fun(pos1=1, pos2=2, key1=3, key2=4)
1 2 3 4

or out of order:

In [23]: fun(key1=1, pos2=2, pos1=3, key2=4)
3 2 1 4

And the positional arguments are all required:

In [24]: fun(3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-5ef8442810a5> in <module>()
----> 1 fun(3)

TypeError: fun() missing 1 required positional argument: 'pos2'

But: Notice that you can either have a required argument with no keyword, or an optional argument with a keyword (and a default). And keyword arguments can also be passed as positional arguments.

This was considered less than ideal – with some APIs, you want to require a keyword be used – and you may have a required argument that you want users to pass as a keyword (rather than positional) argument.

In Python 3 – “keyword only” arguments were added:

https://www.python.org/dev/peps/pep-3102/

So you can do:

def fun (pos1, pos2, *, key1='this'):
    print(pos1, pos2, key1)

Now the user can only provide a value for key1 as a keyword argument. If they pass a third positional argument, it’ll be an error:

In [26]: fun(1,2,3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-057c5c08ae41> in <module>()
----> 1 fun(1,2,3)

TypeError: fun() takes 2 positional arguments but 3 were given

So Python will not just move that third argument along for you. You need to use the keyword:

In [29]: fun(1,2, key1=3)
1 2 3

But you can still let it be the default:

In [30]: fun(1,2)
1 2 this

However, with keyword only arguments you can make it required by providing no default:

def fun(pos1, pos2, *, key1):
    print(pos1, pos2, key1)
In [32]: fun(1,2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-32-0dfacfcc443e> in <module>()
----> 1 fun(1,2)

TypeError: fun() missing 1 required keyword-only argument: 'key1'

So you HAVE to provide it, and you HAVE to provide it as a keyword argument.

In [34]: fun(1,2, key1='that')
1 2 that

What about *args?

Asside from allowing keyword-only paramters with or without defaults, a key addition is that you can now have variable numbers of positional arguments, without them getting confused with the keyword arguments:

def fun (pos1, pos2, *args, key1='this'):
    print(pos1, pos2, args, key1)
In [36]: fun(1,2)
1 2 () this

In [37]: fun(1,2,3)
1 2 (3,) this

Notice how the third argument did NOT get assigned to key1?

And you can pass any number in:

In [39]: fun(1,2,3,4,5,6,7, key1='that')
1 2 (3, 4, 5, 6, 7) that

This is actually the primary motivation for the PEP – it makes a cleaner separation of positional and keyword arguments.

So for ALL the features in one function:

def fun (pos1, pos2, *args, key1='this', **kwargs):
    print(pos1, pos2, args, key1, kwargs)
In [42]: fun(1,2,3,4, this='that', fred='bob')
1 2 (3, 4) this {'this': 'that', 'fred': 'bob'}

or:

In [44]: args = (1,2,3,4)

In [45]: kwargs = {'this':'that', 'fred':'bob'}

In [46]: fun(*args, **kwargs)
1 2 (3, 4) this {'this': 'that', 'fred': 'bob'}

Lots of Flexibility!!