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

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

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

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

### 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
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
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]

``````

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!