Argumentos de longitud variable en Python con *args y **kwargs

Con un número variable de argumentos en una función, ofrecemos una API flexible a otros desarrolladores. En este artículo, definiremos y usaremos funciones con argumentos de longitud variable.

Introducción

Algunas funciones no tienen argumentos, otras tienen múltiples. Hay veces que tenemos funciones con argumentos que no conocemos de antemano. Es posible que tengamos un número variable de argumentos porque queremos ofrecer una API flexible a otros desarrolladores o no conocemos el tamaño de entrada. Con Python, podemos crear funciones para aceptar cualquier cantidad de argumentos.

En este artículo, veremos cómo podemos definir y usar funciones con argumentos de longitud variable. Estas funciones pueden aceptar una cantidad desconocida de entrada, ya sea como entradas consecutivas o argumentos con nombre.

Usando muchos argumentos con *args

Implementemos una función que encuentre el valor mínimo entre dos números. Se vería así:

1
2
3
4
5
6
def my_min(num1, num2):
    if num1 < num2:
        return num1
    return num2

my_min(23, 50)
1
23

Simplemente verifica si el primer número es más pequeño que el segundo número. Si es así, se devuelve el primer número. De lo contrario, se devuelve el segundo número.

Si quisiéramos encontrar un mínimo de 3 números, podemos agregar otro argumento a my_min() y más declaraciones if. Si nuestra función mínima necesita encontrar el número más bajo de cualquier cantidad indeterminada, podemos usar una lista:

1
2
3
4
5
6
7
8
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])
1
2

Mientras esto funciona, nuestros codificadores ahora tienen que encerrar sus números en una lista, lo que no es tan sencillo como cuando teníamos dos o tres argumentos definidos. Obtengamos lo mejor de ambos mundos con argumentos de longitud variable.

Los argumentos de longitud variable, varargs para abreviar, son argumentos que pueden tomar una cantidad de entrada no especificada. Cuando se utilizan, el programador no necesita envolver los datos en una lista o una secuencia alternativa.

En Python, los varargs se definen usando la sintaxis *args. Reimplementemos nuestra función my_min() con *args:

1
2
3
4
5
6
7
8
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)
1
2

Nota: args es solo un nombre, puede nombrar ese vararg cualquier cosa siempre que esté precedido por un solo asterisco (*). Es una buena práctica seguir nombrándolo args para que sea inmediatamente reconocible.

Cualquier argumento que venga después de *args debe ser un argumento con nombre, un argumento al que se hace referencia por su nombre en lugar de por su posición. Con *args ya no puedes hacer referencia a otro argumento por su posición.

Además, solo puede tener en *args tipo vararg en una función.

Puede estar pensando que la solución con *args es muy similar a la solución de lista. Eso es porque *args es internamente una tupla, que es una secuencia iterable similar a las listas. Si desea verificar su tipo, puede ingresar el código en su intérprete de Python:

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

Con *args, podemos aceptar múltiples argumentos en secuencia como se hace en my_min(). Estos argumentos son procesados ​​por su posición. ¿Qué pasaría si quisiéramos tomar varios argumentos, pero hacer referencia a ellos por su nombre? Echaremos un vistazo a cómo hacer esto en la siguiente sección.

Usando muchos argumentos con nombre con **kwargs

Python puede aceptar múltiples argumentos de palabras clave, más conocidos como **kwargs. Se comporta de manera similar a *args, pero almacena los argumentos en un diccionario en lugar de tuplas:

1
2
3
4
5
def kwarg_type_test(**kwargs):
    print(kwargs)

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

La salida será:

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

Mediante el uso de un diccionario, **kwargs puede conservar los nombres de los argumentos, pero no podría mantener su posición.

Nota: Al igual que args, puede usar cualquier otro nombre que no sea kwargs. Sin embargo, las mejores prácticas dictan que debe usar kwargs de manera constante.

Como **kwargs es un diccionario, puedes iterarlo como cualquier otro usando el método .items():

1
2
3
4
5
def kwargs_iterate(**kwargs):
    for i, k in kwargs.items():
        print(i, '=', k)

kwargs_iterate(hello='world')

Cuando se ejecute, nuestra consola mostrará:

1
hello = world

Los argumentos de palabras clave son útiles cuando no está seguro de si un argumento estará disponible. Por ejemplo, si tuviéramos una función para guardar una publicación de blog en una base de datos, guardaríamos información como el contenido y el autor. Una publicación de blog puede tener etiquetas y categorías, aunque no siempre están configuradas.

Podemos definir una función como esta:

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

Alternativamente, permitimos que la persona que llama a la función pase cualquier cantidad de argumentos, y solo asocie etiquetas y categorías si están configuradas:

1
2
3
4
5
6
7
8
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

Ahora que conocemos ambos tipos de soporte para argumentos de longitud variable, veamos cómo podemos combinar los dos en una sola función.

Combinación de Varargs y argumentos de palabras clave

Muy a menudo queremos usar *args y **kwargs juntos, especialmente cuando escribimos bibliotecas de Python o código reutilizable. Por suerte para nosotros, *args y **kwargs funcionan muy bien juntos, y podemos usarlos de la siguiente manera:

1
2
3
4
5
def combined_varargs(*args, **kwargs):
    print(args)
    print(kwargs)

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

Si ejecuta ese fragmento de código, verá:

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

Al mezclar los argumentos posicionales y con nombre, los argumentos posicionales deben aparecer antes de los argumentos con nombre. Además, los argumentos de longitud fija vienen antes que los argumentos de longitud variable. Por lo tanto, obtenemos un orden como este:

  1. Argumentos posicionales conocidos
  2. *argumentos
  3. Argumentos con nombre conocidos
  4. **kwargs

Una función con todo tipo de argumentos puede verse así:

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

Una vez que sigamos ese orden al definir y llamar funciones con varargs y argumentos de palabras clave, obtendremos el comportamiento que esperamos de ellos.

Hasta ahora hemos usado la sintaxis *args y **kwargs para definiciones de funciones. Python nos permite usar la misma sintaxis cuando llamamos funciones también. ¡Veamos cómo!

Desempaquetando argumentos con *args y **kwargs

Consideremos una función add3(), que acepta 3 números e imprime su suma. Podemos crearlo así:

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

Si tenía una lista de números, puede usar esta función especificando qué elemento de la lista se usa como argumento:

1
2
3
magic_nums = [32, 1, 7]

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

Si ejecuta este código, verá:

1
The grand total is 40

Si bien esto funciona, podemos hacerlo más breve con la sintaxis *args:

1
add3(*magic_nums)

El resultado es El gran total es 40, como antes.

Cuando usamos la sintaxis *args en una llamada de función, estamos desempaquetando la variable. Al desempaquetar, queremos decir que estamos extrayendo los valores individuales de la lista. En este caso, sacamos cada elemento de la lista y los colocamos en los argumentos, donde la posición 0 corresponde al primer argumento.

También puede desempaquetar una tupla de manera similar:

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

Si desea desempaquetar un diccionario, debe usar la sintaxis **kwargs.

1
2
3
4
5
6
7
dict_nums = {
    'num1': 32,
    'num2': 1,
    'num3': 7,
}

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

En este caso, Python hace coincidir la clave del diccionario con el nombre del argumento y establece su valor.

¡Y eso es! Puede administrar más fácilmente sus llamadas a funciones desempaquetando valores en lugar de especificar cada argumento que necesita un valor de un objeto.

Conclusión

Con Python, podemos usar la sintaxis *args o **kwargs para capturar un número variable de argumentos en nuestras funciones. Usando *args, podemos procesar un número indefinido de argumentos en la posición de una función. Con **kwargs, podemos recuperar un número indefinido de argumentos por su nombre.

Si bien una función solo puede tener un argumento de longitud variable de cada tipo, podemos combinar ambos tipos de funciones en un solo argumento. Si lo hacemos, debemos asegurarnos de que los argumentos posicionales estén antes que los argumentos con nombre y que los argumentos fijos estén antes que los de longitud variable.

Python también nos permite usar la sintaxis para llamadas a funciones. Si tenemos una lista o una tupla y usamos la sintaxis *args, desempaquetará cada valor como argumentos posicionales. Si tenemos un diccionario y usamos la sintaxis **kwargs, entonces hará coincidir los nombres de las claves del diccionario con los nombres de los argumentos de la función.

¿Estás trabajando en una función que pueda beneficiarse de este tipo de argumentos? ¿O tal vez puede refactorizar una función y hacerla a prueba de futuro? ¡Cuéntanos en qué estás trabajando! !

Licensed under CC BY-NC-SA 4.0