16.01.2020       Выпуск 317 (13.01.2020 - 19.01.2020)       Статьи

Variable-Length Arguments with *args and **kwargs

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

Introduction

Some functions have no arguments, others have multiple. There are times we have functions with arguments we don't know about beforehand. We may have a variable number of arguments because we want to offer a flexible API to other developers or we don't know the input size. With Python, we can create functions to accept any amount of arguments.

In this article, we will look at how we can define and use functions with variable length arguments. These functions can accept an unknown amount of input, either as consecutive entries or named arguments.

Using Many Arguments with *args

Let's implement a function that finds the minimum value between two numbers. It would look like this:

def my_min(num1, num2):
    if num1 < num2:
        return num1
    return num2

my_min(23, 50)
23

It simply checks if the first number is smaller than the second number. If it is, then the first number is returned. Otherwise, the second number is returned.

If we would like to find a minimum of 3 numbers, we can add another argument to my_min() and more if-statements. If our minimum function needs to find the lowest number of any indeterminate amount, we can use a list:

def my_min(nums):
    result = nums[0]
    for num in nums:
        if num < result:
            result = num
    return result

my_min(4, 5, 6, 7, 2)
2

While this works, our coders now have to enclose their numbers in a list, which isn't as straightforward as it was when we had two or three defined arguments. Let's get the best of both worlds with variable length arguments.

Variable-length arguments, varargs for short, are arguments that can take an unspecified amount of input. When these are used, the programmer does not need to wrap the data in a list or an alternative sequence.

In Python, varargs are defined using the *args syntax. Let's reimplement our my_min() function with *args:

def my_min(*args):
    result = args[0]
    for num in args:
        if num < result:
            result = num
    return result

my_min(4, 5, 6, 7, 2)
2

Note: args is just a name, you can name that vararg anything as long as it is preceded by a single asterisk (*). It's best practice to keep naming it args to make it immediately recognizable.

Any argument that comes after *args must be a named argument - an argument that's referenced by its name instead of its position. With *args you can no longer reference another argument by its position.

Also, you can only have on *args type vararg in a function.

You may be thinking that the solution with *args is very similar to the list solution. That's because *args is internally a Tuple, which is an iterable sequence similar to lists. If you'd like to verify it's type, you can enter the code in your Python interpreter:

$ python3
>>> def arg_type_test(*args):
...     print(type(args))
...
>>> arg_type_test(1, 2)
<class 'tuple'>

With *args, we can accept multiple arguments in sequence as is done in my_min(). These arguments are processed by their position. What if we wanted to take multiple arguments, but reference them by their name? We'll take a look at how to do this in the next section.

Using Many Named Arguments with **kwargs

Python can accept multiple keyword arguments, better known as **kwargs. It behaves similarly to *args, but stores the arguments in a dictionary instead of tuples:

def kwarg_type_test(**kwargs):
    print(kwargs)

kwarg_type_test(a="hi")
kwarg_type_test(roses="red", violets="blue")

The output will be:

{'a': 'hi'}
{'roses': 'red', 'violets': 'blue'}

By using a dictionary, **kwargs can preserve the names of the arguments, but it would not be able to keep their position.

Note: Like args, you can use any other name than kwargs. However, best practice dictates that you should consistently use kwargs.

Since **kwargs is a dictionary, you can iterate over them like any other using the .items() method:

def kwargs_iterate(**kwargs):
    for i, k in kwargs.items():
        print(i, '=', k)

kwargs_iterate(hello='world')

When run, our console will show:

hello = world

Keyword arguments are useful when you aren't sure if an argument is going to be available. For example, if we had a function to save a blog post to a database, we would save the information like the content and the author. A blog post may have tags and categories, though those aren't always set.

We can define a function like this:

def save_blog_post(content, author, tags=[], categories=[]):
    pass

Alternatively, we allow the function caller to pass any amount of arguments, and only associate tags and categories if they're set:

def save_blog_post(content, author, **kwargs):
    if kwargs.get('tags'):
        # Save tags with post
        pass

    if kwargs.get('categories'):
        # Save categories with post
        pass

Now that we have a grasp of both types of support for variable length arguments, let's see how we can combine the two in one function.

Combining Varargs and Keyword Arguments

Quite often we want to use both *args and **kwargs together, especially when writing Python libraries or reusable code. Lucky for us, *args and **kwargs play nicely together, and we can use them in the following way:

def combined_varargs(*args, **kwargs):
    print(args)
    print(kwargs)

combined_varargs(1, 2, 3, a="hi")

If you run that code snippet you'll see:

(1, 2, 3)
{'a': 'hi'}

When mixing the positional and named arguments, positional arguments must come before named arguments. Furthermore, arguments of a fixed length come before arguments with variable length. Therefore, we get an order like this:

  1. Known positional arguments
  2. *args
  3. Known named arguments
  4. **kwargs

A function with all types of arguments can look like this:

def super_function(num1, num2, *args, callback=None, messages=[], **kwargs):
    pass

Once we follow that order when defining and calling functions with varargs and keyword arguments, we'll get the behavior we expect from them.

So far we've used the *args and **kwargs syntax for function definitions. Python allows us to use the same syntax when we call functions as well. Let's see how!

Unpacking Arguments with *args and **kwargs

Let's consider a function add3(), that accepts 3 numbers and prints their sum. We can create it like this:

def add3(num1, num2, num3):
    print("The grand total is", num1 + num2 + num3)

If you had a list of numbers, you can use this function by specifying which list item is used as an argument:

magic_nums = [32, 1, 7]

add3(magic_nums[0], magic_nums[1], magic_nums[2])

If your run this code, you will see:

The grand total is 40

While this works, we can make this more succinct with *args syntax:

add3(*magic_nums)

The output is The grand total is 40, just like before.

When we use *args syntax in a function call, we are unpacking the variable. By unpacking, we mean that we are pulling out the individual values of the list. In this case, we pull out each element of the list and place them in the arguments, where position 0 corresponds to the first argument.

You can also similarly unpack a tuple:

tuple_nums = (32, 1, 7)
add3(*tuple_nums) # The grand total is 40

If you would like the unpack a dictionary, you must use the **kwargs syntax.

dict_nums = {
    'num1': 32,
    'num2': 1,
    'num3': 7,
}

add3(**dict_nums) # The grand total is 40

In this case, Python matches the dictionary key with the argument name and sets its value.

And that's it! You can easier manage your function calls by unpacking values instead of specifying each argument that needs a value from an object.

Conclusion

With Python, we can use the *args or **kwargs syntax to capture a variable number of arguments in our functions. Using *args, we can process an indefinite number of arguments in a function's position. With **kwargs, we can retrieve an indefinite number of arguments by their name.

While a function can only have one argument of variable length of each type, we can combine both types of functions in one argument. If we do, we must ensure that positional arguments come before named arguments and that fixed arguments come before those of variable length.

Python allows us to use the syntax for function calls as well. If we have a list or a tuple and use the *args syntax, it will unpack each value as positional arguments. If we have a dictionary and use **kwargs syntax, then it will match the names of the dictionary keys with the names of the function arguments.

Are you working on a function that can benefit from these type of arguments? Or maybe you can refactor a function and make it future proof? Let us know what you're working on!






Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus