Difference between args and kwargs in Python
Introduction
I think the reason why this topic can be confusing specially to beginners is that it mixes different concepts together. It should not be hard to understand with proper context. In my opinion, to better understand the difference between Args and Kwargs in Python, the following points must be crystal clear…
- Positional vs keyword arguments
- Packing and unpacking
- What is being sent vs what the function expects
Let us comment on each point…
- A positional argument means the involved function expects an argument at a specific position for example: func(x, y, z) has x as the first argument, y as the second and z as the third. If we need to pass 1 for x, 2 for y and 3 for z then we have to call the function respecting the order like func(1, 2, 3). On the other hand, a keyword argument means we provide an argument name and value together when calling the function therefore the order is irrelevant. For example, we can call the function as func(z=3, x=1, y=2) and everything should work as expected. So, rule 1: be aware of what type of arguments you are passing
- What about packing and unpacking? packing refers to combining individual positional arguments into a list or individual keyword arguments (i.e. key value pairs) into a dictionary. On the other hand, unpacking is the opposite operation in which we unwrap a list to individual values or a dictionary to key value pairs. Let us not overthink, it is as simple as that. Now syntax wise ! In Python *Args (you can use any argument name for example *x) and ** Kwargs are used for packing when we call a function (calling end) and unpacking in the function definition (receiving end). If you think about it ! there is nothing magical here. It is just a convenient language feature that provides flexibility. So rule 2: In your mind, imagine a list or dictionary being broken into values or key value pairs or the opposite operation where values or key value pairs are combined back to a list or dictionary.
- The third rule that you need to put in mind is to be 100% sure about what you are sending to the function and what the function is expecting.
If you apply all these rules all together, there is going to be less chance for mistakes. Let us now dive into some examples…
Example code
We can call a function with multiple parameters using a list or tuple. Here is an example…
1 2 3 4 5 6 7 8 |
def func(lst): for val in lst: print(val) lst = [1, 2, 3] tpl = (1, 2, 3) func(lst) func(tpl) |
The function expects one argument of type list or tuple. Executing the code should print the following output…
1 2 3 |
# 1 # 2 # 3 |
A dictionary can also be used to pass multiple named parameters to a function. Here is an example…
1 2 3 4 5 6 7 8 9 |
def func(dct): # Note that this is a Python 2.x syntax # If you are using Python 3.x then use # dct.items() for key, val in dct.iteritems(): print(key, val) dct = {"x":1, "y":2, "z":3} func(dct) |
The function expects one argument of type dictionary. Executing the code should print the following output…
1 2 3 |
# ('y', 2) # ('x', 1) # ('z', 3) |
Calling a function with positional and keyword arguments . Let us take an example…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
def func(p1=0, p2=0, p3=0): print(p1, p2, p3) # This function expects 3 parameters in # the order p1, p2, p3 with default values # of 0. It should print (1, 2, 3) no magic here func(1, 2, 3) # No named parameters so p1 is going to # receive 1, p2 is going to receive 2 # and p3 is going to default to 0 # So the output should be (1, 2, 0) func(1, 2) # This should print (1, 2, 3) even though the # order is not respected simply because we did # not rely on parameter position however we # relied on parameter name func(p3=3, p1=1, p2=2) # This is a mix between positional # and named parameters. In this case # p1 got a 1 because it is the first one. # p2 and p3 are not in order however using # the parameter name makes sure we still # print (1, 2, 3) func(1, p3=3, p2=2) # In this case p2 got 2 and p1, p3 were # omitted so they are going to get the # default values so the output should # be (0, 2, 0) func(p2=2) # We cannot use a non-keyword arg # after a keyword arg. We cannot do # something like: func(1, p2=2, 3) # Python 2.x doesn't have a way to # define keyword-only arguments # In Python 3.x it says: # SyntaxError: positional argument # follows keyword argument # What if we call the function with a list ? # Are we going to get (1, 2, 3) ? # The answer is NO. We are going to # get ([1, 2, 3], 0, 0) p1 will receive # the list [1, 2, 3] while p2 and p3 # will take the default values of 0 # since they were never used lst = [1, 2, 3] func(lst) # What about this ? Yes this is going # to print (1, 2, 3) ! why ? # This is what we are going to discuss # later. The * operator unpacks the list # back to 3 items so p1 will get 1, p2 # will get 2 and p3 will get the 3 func(*lst) # Similarly we can unpack the dictionary # so this will print (1, 2, 3). Pay extra # attention to the key names ? p1, p2, p3 dct = {"p1":1, "p2":2, "p3":3} func(**dct) # We can not unpack a dictionary with # different key names because they are # not going to match the parameter names # in our function dct = {"x":1, "y":2, "z":3} # If we call the function using the # above dictionary. We are going to # get the following error: # TypeError: func() got an unexpected # keyword argument 'y' |
Let us now define a function that uses *Args and **Kwargs…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
def func(p1=0, *args, **kwargs): print(p1) print("--") for a in args: print(a) print("--") # use kwargs.items() if you are using Python 3.x for key, val in kwargs.iteritems(): print(key, val) lst = [1, 2, 3] tpl = (4, 5, 6) dct = {"x":7, "y":8, "z":9} # No parameters provided, this is # going to print 0 which is the # default value of p1. If p1 has no # default value then an error message # is generated because the function # expects at least one parameter # which is p1 func() print # This is going to print 1 func(1) print # In this case p1 is not provided # so the default value of 0 is going # to be printed. The variable number # of arguments args is not provided # so no printing. The variable number # of key word arguments kwargs is provided # with only one keyword which is x = 4 # so this is going to print ('x', 4) func(x=4) print # This is going to print # 1, 2, 3, ('x', 4), ('y', 5), ('z', 6) why ? # 1 is fixed positional argument # 2 and 3 are variable positional # 4, 5 and 6 are variable key worded func(1, 2, 3, x=4, y=5, z=6) print # First argument is a fixed positional # argument so 1 is printed # *tpl is going to unpack the tuple (4, 5, 6) # **dct is going to unpack the dictionary # {"x":7, "y":8, "z":9} # so it is like calling the function as # func(1, 4, 5, 6, x=7, y=8, z=9) # so the final output should be # 1, 4, 5, 6, ('x', 7), ('y', 8), ('z', 9) func(1, *tpl, **dct) print # Note that as you unpack, a name # conflict may happen assigning # multiple vlaues to a keyword argument # here is an exmample, you may think it # is going to work but it does not ! why ? # when the tuple is unpacked, the first # element 4 goes for p1 and p1=1 is another # assignment which is a conflict # func(p1=1, *tpl, **dct) # This is going to work and will print # 4, 5, 6, ('p2', 2), ('x', 7), ('y', 8), ('z', 9) # A default of 0 for the first positional # argument is not going to be printed # because as we unpack the tuple, # the 4 goes for it func(p2=2, *tpl, **dct) |
To better understand what really happens is to know that unpacking happens at call time then in the function definition packing takes place. Keeping that in mind helps in tracking what values goes to what variables. Take the previous example:
1 2 3 4 5 6 7 8 9 10 |
# -- Fixed number # 4 # -- Packed list 5, 6 and not 4, 5, 6 # 5 # 6 # -- Packed dictionary note that p2 was added # ('y', 8) # ('x', 7) # ('z', 9) # ('p2', 2) |
Let us summarize…
Summary
- Rule 1: be aware of what type of arguments you are passing
- Rule 2: In your mind, imagine a list or dictionary being broken into values or key value pairs or the opposite operation where values or key value pairs are combined back to a list or dictionary
- Rule 3: be sure what you are sending to a function and what the function is expecting
That is it for today. Thanks for visiting. Please use the comments section for feedback and questions.