Using * args and ** kwargs in python

Question:

I've seen code examples where functions take *args and **kwargs . I don't understand that syntax or in what cases it should be used.

Although I have tried to read about it, I have only found information in English that I did not understand , could someone explain it to me in Spanish in a few words?

Answer:

Functions in python can receive parameters of two different types: those that when invoking the function are assigned by the position they occupy in the list of parameters, and those that when invoking are assigned by the name of the parameter.

For example, consider the following function with five parameters. The first two do not have default values, the last three do:

def ejemplo(a, b, c=1, d=0, e=2):
    print("He recibido a={}, b={}, c={}, d={}, e={}".format(a,b,c,d,e))

If we invoke it like this: ejemplo(10, 20) , we will be using the positional method, so that a will take the value 10 and b the value 20 . The remaining parameters will take the default value specified in the function declaration. We can also invoke it like this: ejemplo(10, 20, 30) , in that case 30 will go to c , since positionally it is the third parameter.

But we can also invoke it like this: ejemplo(b=20, a=10, d=4) . In this case the order in the invocation does not matter, since we are using the names of the parameters to specify which parameter each value goes to. Of course, we necessarily have to assign values ​​to a and b since they do not have a default value in the function declaration.

But there are times when we don't know in advance how many parameters the function is going to receive. A typical case is when the function in question receives as a parameter another function that it must invoke, along with the parameters that must be passed to that other function. This kind of "wrapper" function from another is used a lot in decorators.

I'm not going to get into the wrapper function yet. Instead just ask yourself that you have a function that can be called with a different number of parameters and you don't know how many in advance.

For this, Python offers the possibility of declaring a single parameter in the function with an asterisk in front of it. It's customary to call *args to that parameter, but you can actually call it whatever you want. What will happen is that this parameter will be a tuple with all the arguments that you have passed in the function during the call. For example:

def ejemplo2(*args):
   print("He recibido estos parámetros: `, args)

This function will print the tuple it receives as a parameter. You can invoke that function like this: ejemplo2(1,2,3) and it will print "He recibido estos parámetros: (1, 2, 3)" .

However, if you try to call the function using the assign syntax, for example like this: ejemplo2(a=1, b=2, c=3) , it will fail, since the asterisk in *args indicates that this variable will collect all the positional parameters, but will not pick up any non-positional. In fact the declaration of ejemplo2() prevents you from passing non-positional parameters to it.

For the latter there is the double asterisk syntax. If you precede a parameter with ** , that parameter will collect all the arguments that have been passed to the function in a non-positional way (with name), in a single dictionary type data. In that dictionary the keys are the names of the parameters. Typically this other parameter is called **kwargs , but the name again could be **kwargs else.

So, the following example:

def ejemplo3(*args, **kwargs):
    print("He recibido estos parámetros posicionales:", args)
    print("Y estos no posicionales:", kwargs)

It can be invoked in many ways. ejemplo3(1, 2, a=20, b=12) for example: ejemplo3(1, 2, a=20, b=12) . In that case, the screen would display:

He recibido estos parámetros posicionales: (1, 2)
Y estos no posicionales: { 'a': 20, 'b': 12 }

To finish completing the puzzle, it only remains to say that the syntax * or ** can also be used in the invocation of a function to "expand" a tuple or a dictionary and convert it into a series of arguments to pass to the function. You can do this by calling another function regardless of whether that other function used * , ** in its declaration or used the "normal" way of declaring parameters.

Consider for example the ejemplo function at the beginning. I can also invoke it like this:

posicionales = (10, 20)
no_posicionales = { 'c': 30, 'd': 40 }
ejemplo(*posicionales, **no_posicionales)

The first * expand the tuple (10, 20) to fill two arguments of the function. The ** that comes next will expand the dictionary to supply additional named parameters. The call therefore is equivalent to:

ejemplo(10, 20, c=30, d=40)

Thanks to this, you can make a function that receives another as a parameter, together with the arguments that must be passed to that other, all without the "wrapper" function knowing in advance the declaration of the "wrapped" function.

Example:

def envoltorio(funcion_a_ejecutar, *args, **kwargs):
    print("Ejecuto la función dada")
    r = funcion_a_ejecutar(*args, **kwargs)
    print("Que ha retornado", r)
    return r

And I can use it like this:

envoltorio(ejemplo, 1, 2, d=40)

so envoltorio will receive tuple (1,2) in args and dictionary {'d':40} in kwargs , and when it executes the inner function it will expand that tuple and dictionary back into the appropriate arguments, so in this case will execute ejemplo(1,2,d=40) .

Scroll to Top