Use of * args and ** kwargs in python [closed]

2

I have seen examples of code in which the functions receive parameters *args and **kwargs . I do not understand that syntax nor in what cases it should be used.

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

    
asked by Edgar Ocampo 20.09.2018 в 07:48
source

1 answer

6

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

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 declaration of the function. We can also invoke it like this: ejemplo(10, 20, 30) , in that case the 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 to which parameter each value goes. Of course, we have to assign values to a and b since they have no default value in the declaration of the function.

But there are times when we do not know in advance how many parameters the function will receive. A typical case is when the function in question receives as a parameter another function that must be invoked, together with the parameters that must be passed to that other function. This type of "wrapping" function of another is widely used in decorators.

I'm not going to enter the theme of the wrapping function yet. Instead, just consider that you have a function that can be invoked with a different number of parameters and you do not know in advance how many.

For this Python offers the possibility to declare a single parameter in the function with an asterisk in front. It is customary to call *args to that parameter, but in reality you can call it as 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. So, 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: ejemplo2(a=1, b=2, c=3) , it will fail, since the asterisk of *args indicates that this variable will pick up all positional parameters , but will not pick up any non-positional. In fact, the declaration of ejemplo2() prevents you from passing non-positional parameters.

For the second is the syntax with double asterisk. 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 data of type dictionary. In that dictionary the keys are the names of the parameters. Typically this other parameter is called **kwargs , but the name again could be another.

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. Let's say: ejemplo3(1, 2, a=20, b=12) . In that case, the screen would appear:

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 for move to the function. You can do this by calling to another function regardless of whether that other function has used * , ** in its declaration or has used the "normal" way of declaring parameters.

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

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

The first * will expand the tuple (10, 20) to fill in two arguments of the function. The next ** will expand the dictionary to provide additional named parameters. The call therefore amounts 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 that envoltorio will receive the tuple (1,2) in args and the dictionary {'d':40} in kwargs , and when you execute the internal function it will expand that tuple and dictionary in the appropriate arguments, so which in this case will execute ejemplo(1,2,d=40) .

    
answered by 20.09.2018 в 09:33