Only registred users can make comments

Learn *args and **kwargs in Python

Learn *args and **kwargs in Python

Introduction

In this article, we are discussing args and kwargs that you have probably seen in various Python codes.

Normally, we pass arguments to functions and classes and we do something with the data we sent in.

Beginners usually learn how to define positional arguments that can be called by their position in the function call.

Here is a very silly function that returns a number of lemons based on the integer we sent in.

def give_me_lemons(num): # positional argument
    assert num > 0, 'Can\'t give you any lemons'
    lemon_word = 'lemon' if get_sum == 1 else 'lemons'
    return f"I\'m giving you {get_sum} {lemon_word}"
    

print(give_me_lemons(2))

Output

I'm giving you 2 lemons

If you add 0 or a negative number, you would get the following:

print(give_me_lemons(0))

Output:

AssertionError: Can't give you any lemons

 

What if we want to add any number of arguments? That wouldn't work in our current implementation, simply because our give_me_lemons() function requires one argument only (positional argument):

print(give_me_lemons(3, 5))

Output:

TypeError: give_me_lemons() takes 1 positional argument but 2 were given

How can we provide any number of arguments? We will solve it with an unpacking operator (*)

 

Any number of arguments - *args

The use case now is that we want to pass one or multiple numbers to our silly function and it will calculate the sum of lemons it wants to give us.

Be aware that the word *args is not a keyword, you can give it any name. I have deliberately used nums in the following example.

def give_me_lemons(*nums:int): # providing *args, which is not a keyword, you can use whatever name. (*) is an unpacking operator
    get_sum = sum(nums) #sum the provided numbers
    assert get_sum > 0, 'Can\'t give you any lemons' # checking so no zero or negative number is provided, otherwise no lemons
    lemon_word = 'lemon' if get_sum == 1 else 'lemons' # just checking singular or plural
    return f"I\'m giving you {get_sum} {lemon_word}" # return lemon(s)
    

print(give_me_lemons(3, 5, 8))

Output:

I'm giving you 16 lemons

 


❗ (*) is an unpacking operator, it unpacks any number of arguments.


We will use add print(type(nums)to check what type of object nums is.

def give_me_lemons(*nums:int): # providing *args, which is not a keyword, you can use whatever name. (*) is an unpacking operator
    print(type(nums))
    get_sum = sum(nums) #sum the provided numbers
    assert get_sum > 0, 'Can\'t give you any lemons' # checking so no zero or negative number is provided, otherwise no lemons
    lemon_word = 'lemon' if get_sum == 1 else 'lemons' # just checking singular or plural
    return f"I\'m giving you {get_sum} {lemon_word}" # return lemon(s)
    

print(give_me_lemons(3, 5))

Output:

<class 'tuple'>
I'm giving you 8 lemons

The nums is a tuple which means it's an immutable object. We cannot change the value in nums object:

def give_me_lemons(*nums:int): # providing *args, which is not a keyword, you can use whatever name. (*) is an unpacking operator
    print(type(nums))
    nums[0] = 4
    get_sum = sum(nums) #sum the provided numbers
    assert get_sum > 0, 'Can\'t give you any lemons' # checking so no zero or negative number is provided, otherwise no lemons
    lemon_word = 'lemon' if get_sum == 1 else 'lemons' # just checking singular or plural
    return f"I\'m giving you {get_sum} {lemon_word}" # return lemon(s)
    

print(give_me_lemons(3, 5))

Output:

TypeError: 'tuple' object does not support item assignment

❗Once a tuple is created, you cannot change its values.


Now were have learned what *args is, but what is **kwargs? We will cover it in the next section.

Dictionary - **kwargs

Even in this case, "kwargs" is not a keyword, you can give it any name.

We will go straight into finding out what kind of object **kwargs is by modifying our function a little bit.

def give_me_lemons(**kwargs): # providing **kwargs, which is not a keyword, you can use whatever name. (**) is an unpacking operator
    print(type(kwargs))
    return kwargs.items()


print(give_me_lemons(bucket_1=13, bucket_2=24, bucket_3=10)) # we need to provide key-value pair

Output:

<class 'dict'>
dict_items([('bucket_1', 13), ('bucket_2', 24), ('bucket_3', 10)])

As you have noticed, the unpacking operator (**) is used for dictionary objects. Since it expects a dictionary,  we need to pass key-value pairs.

If we want to sum the number of lemons in each bucket, we'll need to use kwargs.values() method to get the actual amount of lemons in each bucket.

def give_me_lemons(**kwargs): # providing **kwargs, which is not a keyword, you can use whatever name. (**) is an unpacking operator

    get_sum = sum(kwargs.values()) #sum the provided numbers
    assert get_sum > 0, 'Can\'t give you any lemons' # checking so no zero or negative number is provided, otherwise no lemons
    lemon_word = 'lemon' if get_sum == 1 else 'lemons' # just checking singular or plural
    return f"I\'m giving you {get_sum} {lemon_word}" # return lemon(s)
    

print(give_me_lemons(bucket_1=13, bucket_2=24, bucket_3=10)) # we need to provide key-value pair

Output:

I'm giving you 47 lemons

 


❗ Dictionaries in Python have the following built-in methods to find out items, keys, and values:

  • x.items()
  • x.keys()
  • x.values()

Ordering Matters

In some cases, you need to have several types of arguments, both positional and changeable. When writing those functions, you need to consider the order of how you specify function parameters.

def test_me(price:int, tax:int, *args, **kwargs): # you need to have it in this order *args, **kwargs
    unpack_args = args
    unpack_kwargs = kwargs
    sum_lemons = sum(unpack_kwargs.values())
    
    
    return f"Price: ${price}, Tax: ${tax}, Orders: {unpack_args}, Items: {unpack_kwargs} Sum: {sum_lemons}"

print(test_me(2, 20, 2, 4, bucket_1=13, bucket_2=24))

Output:

Price: $2, Tax: $20, Orders: (2, 4), Items: {'bucket_1': 13, 'bucket_2': 24} Sum: 37

 

The following would throw an error:

def test_me(price:int, tax:int, **kwargs, *args): # you need to have it in this order *args, **kwargs
    unpack_args = args
    unpack_kwargs = kwargs
    sum_lemons = sum(unpack_kwargs.values())
    
    
    return f"Price: ${price}, Tax: ${tax}, Orders: {unpack_args}, Items: {unpack_kwargs} Sum: {sum_lemons}"

print(test_me(2, 20, 2, 4, bucket_1=13, bucket_2=24))

Output:

def test_me(price:int, tax:int, **kwargs, *args): # you need to have it in this order *args, **kwargs
                                              ^
SyntaxError: invalid syntax

 


❗You need to keep to following ordering rules:

  1. Positional arguments
  2. *args arguments
  3. **kwargs arguments

Summary

We have learned the following:

  • positional arguments
  • *args: unpacking tuple items
  • **kwargs unpacking dict items, using methods as x.items(), x.keys(), x.values()
  • Argument order matters when we define the parameters of a function 

 

About the Author

Aleksandro Matejic, Cloud Architect, began working in IT Industry over 20y ago as a technical consultant at Lindahl Rothoff in southern Sweden. Since then, he has worked in various companies and industries like Atea, Baxter, and IKEA having various architect roles. In his spare time, Aleksandro is developing and running devoriales.com, a blog and learning platform launched in 2022. In addition, he likes to read and write technical articles about software development and DevOps methods and tools. You can contact Aleksandro by paying a visit to his LinkedIn Profile.

Comments