Variadic functions, or officially argument packing, is a way to pass variable number of arguments into a function in Python.

Using *args for Positional Arguments

In a variadic function, extra arguments are collected in a tuple that we can write as *args in the argument list:

def my_sum(*args):
  result = 0
  for arg in args:
      result += arg
  return result
 
my_sum() # 0
my_sum(1, 2, 3) # 6
my_sum(1, 10, 100) # 111

We can also have normal positional arguments along with variadic arguments. And the variadic argument does not necessarily to be called arg:

def my_sum2(first, *rest):
  return first + sum(rest)
 
my_sum2() # Error
my_sum2(1, 2, 3) # 6
my_sum2(1, 10, 100) # 111

Using **kwargs for Keyword Arguments

Functions can also accept a variable number of keyword arguments. We can pass to function a **kwargs, which has the type of a dict.

def my_sum3(first, *rest, **kwargs):
  if kwargs["debug"]:
    print(first, rest)
  return first + sum(rest)
 
 # print "1, (2, 3)" and then returns 6
my_sum3(1, 2, debug = True)

Unpacking with * and **

* and ** can also be used as unpacking operators to unpack iterables and dictionaries, correspondingly. For example,

my_list = [1, 2, 3]
print(*my_list) # 1 2 3

will unpack the call statement into print(1, 2, 3).

If we unpacking a wrong number of element as arguments to pass into a function, Python will complain:

def my_sum(a, b, c):
    print(a + b + c)
 
my_list = [1, 2, 3, 4]
my_sum(*my_list) # Error

We can even use multiple unpack operators in a single expression and we can use them in places other than function call. For example:

xs = [1, 2, 3]
ys = [11, 22, 33]
[*xs, *ys] # [1, 2, 3, 11, 22, 33]

Another cool use of the unpack operator is to “pattern match” Python list into different parts:

my_list = [1, 2, 3, 4, 5, 6]
 
a, *b, c = my_list
# a = 1, b = [2, 3, 4, 5], c = 6

Remember that * works for any iterable object rather than just lists and tuples. For example, we can use it to unpack strings:

[*"Lesley"] # ['L', 'e', 's', 'l', 'e', 'y']

For dictionaries, the unpack operator ** works similarly. For example, we can merge two dictionary with the following:

my_first_dict = {"A": 1, "B": 2}
my_second_dict = {"C": 3, "D": 4}
my_merged_dict = {**my_first_dict, **my_second_dict}
 
print(my_merged_dict)

References