Python args vs kwargs
Today’s grammar lesson: what is an iterable?
To understand args (and kwargs) you have to understand iterables. An uncommon word with a simple principle: repeat an action. That’s it.
Say someone comes to you and asks ‘create a list of actions for a number of objects.’
You say ‘Sure. How many objects?’
They respond with ‘Dunno yet. Can’t you just set something up that will cope with whatever we fire at you?’
Enter args
. Although I could have just said enter ‘bish
‘ or ‘bash
‘ or ‘bosh
‘. The name can be any python-valid name – one word ie no spaces, made of letters, numbers or underscores, and doesn’t start with a number. What marks it out as ‘an iterable list of indeterminate length’ is the unpacking operator: *
(This is called a ‘splat’ in Ruby/Perl 6. JavaScript has the same idea but uses an ellipsis ie three dots … you’re getting a serious grammar work-out today)
Try the following;
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish)MyArgs.my_iterable_things(3, 6, 7.0, 'six', 6)
It should print out this:
3
6
7.0
six
6
If you remove the unpacking operator like this:
def my_iterable_things(bish):
You’ll get this:
Traceback (most recent call last):
File "./args_kwargs.py", line 11, in
MyArgs.my_iterable_things(3, 6, 7.0, 'eight', 6)
TypeError: my_iterable_things() takes 1 positional argument but 5 were given
It’s telling your that you have one variable expected (called bish
) but you’re trying to feed it five. Ok we need the asterisk, but what is a *bish
? Add the following print-lines and run:
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish))MyArgs.my_iterable_things(3, 6, 7.0, 'six', 6)
Giving us:
3
6
7.0
six
6
object is a type of:
<class 'tuple'>
So *bish
is a tuple, and we should be able to do tuple-y things to it. Add a bish.count()
to see how many times ‘6’ appears, and a bish.index() to find the location of ‘six’:
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish)) print(bish.count(6))
print(bish.index('six'))MyArgs.my_iterable_things(3, 6, 7.0, 'six', 6)
This gives us:
3
6
7.0
six
6
object is a type of:
<class 'tuple'>
2
3
ie there are two instances of ‘6’ in our list of arguments. ‘six’ is of course a string hence a str object, not an integer. Just to check, change ‘six’ into ‘6’ in quotes and run again. Still two items, since ‘6’ in quotes is a string.
And the position of ‘six’ is index 3 ie the fourth one in.
Putting count(9)
will return 0 as 9 doesn’t exist ie ‘no equivalent values found’. However putting a value into the index(-1)
method that doesn’t exist will blow up with this:
ValueError: tuple.index(x): x not in tuple
Note that bish.index(-1)
is different from bish[-1]
. The first is trying to find the index location of a value (in this case -1) and can’t find it – hence the complaint from the compiler saying x not in tuple
. The second is finding the value located at a specific index – in this case -1, or one left of zero. In this case that definitely exists, it’s 6.
This also means that slicing will work. We can ask for the arguments from one up to (but not including) three:
print(bish[1:3])
and it will return (6, 7.0)
.
If you’ve Googled ‘tuple’ you’ll probably be aware that it’s any number of objects, separated with commas, in round brackets. What happens if we type in just one value, or even zero values? Open a new python file and add this:
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish))MyArgs.my_iterable_things(34)
You should get this:
34
object is a type of:
<class 'tuple'>
Now if we change the input to this (ie delete ’34’):
MyArgs.my_iterable_things()
We still get object is of class type ‘tuple’. What gives?
Well, in order to guarantee that a tuple is always delivered, some sleight-of-hand has to happen when one or zero objects are provided. Add the following lines to the end:
my_vars = 3, 4, 5print(type(my_vars))
print(my_vars)
You should get both objects as a type of class ‘tuple’, with tuple brackets (round ones) added:
object is a type of:
<class 'tuple'>
<class 'tuple'>
(3, 4, 5)
Now reduce the variables to just one:
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish))MyArgs.my_iterable_things(3)my_vars = 3print('object is a type of: ')
print(type(my_vars))
print(my_vars)
We now get the second one as an integer — and no brackets:
3
object is a type of:
<class 'tuple'>
object is a type of:
<class 'int'>
3
We can’t put nothing into a variable (a double negative?), but we can call the method and not give it any variables:
class MyArgs(): def my_iterable_things(*bish):
for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish))MyArgs.my_iterable_things()# my_vars # won't work!# print('object is a type of: ')
# print(type(my_vars))
# print(my_vars)
We’ve given it nothing and it’s still coming back as a tuple!
object is a type of:
<class 'tuple'>
Well a tuple is ‘one or more objects separated by commas’, so if we were to add a comma after the ‘3’ like this:
class MyArgs(): def my_iterable_things(*bish): for my_single_bish in bish:
print(my_single_bish) print('object is a type of: ')
print(type(bish))
print(bish)MyArgs.my_iterable_things(3)my_vars = 3,print('object is a type of: ')
print(type(my_vars))
print(my_vars)
We would get:
3
object is a type of:
<class 'tuple'>
(3,)
object is a type of:
<class 'tuple'>
(3,)
So it looks like we’re going to end up with a tuple. Break out your tuple skills.
What about kwargs?
Sending multiple values of an unknown number is easy when you use the unpacking operator. It works with all single-value items. But what if you want to send some default values, ie keys and values (aka keyword arguments)? That uses two asterisks:
class MyArgs(): def my_iterable_things(**bosh):
for my_single_bosh in bosh:
print(my_single_bosh)MyArgs.my_iterable_things(print = 10, color = True, quality = 'draft')
This will print out:
print
color
quality
Close, but no cigar. We wanted the actual values printed out:
for my_single_bosh in bosh.values():
This now gives us:
10
True
draft
That’s more like it. But where did I get this .values()
idea from? When you use a double-star your list of keyword pairs is assembled as a dict (short for dictionary), hence methods that work on dicts also work on kwargs:
class MyArgs(): def my_iterable_things(**bosh):
for my_single_bosh in bosh.values():
print(my_single_bosh) print('object is a type of: ')
print(type(bosh))MyArgs.my_iterable_things(print = 10, color = True, quality = 'draft')
This will give you:
10
True
draft
object is a type of:
<class 'dict'>
Cool. We can Google how to use dict objects, but what if we want to use both? There’s just one rule: args come before kwargs (it simply won’t compile if you switch them — try it):
class MyArgs(): def my_iterable_things(*args, **kwargs):
for my_single_arg in args:
print(my_single_arg) print('object is a type of: ')
print(type(args)) for my_single_kwarg in kwargs.values():
print(my_single_kwarg) print('object is a type of: ')
print(type(kwargs))MyArgs.my_iterable_things(3, 6, 7.0, 'six', 6, print = 10, color = True, quality = 'draft')
This gives us:
3
6
7.0
six
6
object is a type of:
<class 'tuple'>
10
True
draft
object is a type of:
<class 'dict'>
There’s one final wrinkle to be aware of. The kwargs is straightforward as we can grab them by the keywords used. If we print(kwargs['quality'])
then we get back ‘draft
‘.
If our values don’t have keywords then we need tuples because the order can’t change. Suppose we were doing a percentage calculation that relied on the 0th argument being divided by the first. At the moment we would get 3/6 ie 50%. If however the order was flexible (?) then we could get 6/3 ie 200%. That would be a problem.
So. *args is a tuple, **kwargs is a dict. The names are conventions, the asterisks and order are not.
Any use? Comments/questions, or claps are nice?