En este post explicaré para qué sirven y cómo se usan los *args y **kwargs en las llamadas a las funciones de Python 3.
Esto permite llamar a funciones que reciben un número indefinido de argumentos; y también cuando necesitamos múltiples argumentos opcionales. Vamos a ver algunos ejemplos.
Las funciones y los argumentos
Recordemos que las funciones son algo así:
def funcion(un_argumento)
Y que pueden tener más de un argumento:
def sumar(numero1, numero2)
O no tener ninguno:
def funcion()
Dentro de las funciones, se pueden usar a los argumentos para lo que sea necesario.
Explicando *args en Python
Este nombre no es obligatorio (la sintaxis sí), podría ser, por ejemplo, *argumentos
o cualquier otra cosa siempre y cuando se ponga el asterisco antes del nombre.
Para ejemplificar esto, pongamos el ejemplo de una función que calcule el promedio de algunos valores. ¿Cuántos valores? no sabemos, por eso es que recibimos infinitos argumentos gracias a *args.
Dejaré que el código hable por mí:
"""
Explicar *args en Python con una
función
@author parzibyte
"""
def promedio(*numeros):
#Saber cuántos elementos hay, imaginemos que los argumentos son un arreglo
cantidad_de_elementos = len(numeros)
#Evitar división entre 0
if cantidad_de_elementos <= 0:
return 0
suma = 0
# Justo aquí iteramos los argumentos o *args
for numero in numeros:
suma += numero
return suma / cantidad_de_elementos
print(promedio(80, 97, 114, 122, 105, 98, 117, 103)) #104.5
print(promedio(50, 20)) # 35
print(promedio(1, 5)) # 3
print(promedio(20, 1500)) # 760
La función es definida, pero con un argumento un poco especial: *numeros
. Eso le indica a Python que sea una función que puede recibir desde 0 hasta infinitos argumentos. Así, se puede invocar a la misma con cualquier número de argumentos.
En este caso fue con argumentos enteros, pero podrían ser cadenas, URL’s de imágenes para descargar, etcétera. Lo importante o interesante es que podemos recorrer los argumentos
No es que esto sea algo nuevo, las funciones con un número indefinido
Más sobre *args en Python
Sólo para mencionar que podemos tener una función que reciba tanto un argumento posicional y los demás infinitos.
Por ejemplo, una que calcule promedios y reciba el nombre de una persona (sólo para ejemplificar) quedaría así:
def promedio(nombre, *argumentos)
Por cierto, no se puede tener dos veces a *arg
s. Lo siguiente es erróneo:
def funcion(*argumentos, *otros_argumentos)
Y no se pueden tener a argumentos posicionales después de los argumentos infinitos. Lo siguiente no es correcto:
def function(*argumentos, un_argumento)
Uso de **kwargs en Python
Igualmente no es necesario poner kwargs, sino los dos asteriscos.
Por ejemplo, podría usarse el nombre de **argumentos_con_nombre ya que más o menos eso quieren decir (no literalmente, porque en inglés significa keyword arguments)
Estos kwargs en Python son usados para dos cosas que veremos a continuación.
Cuando una función recibe muchos argumentos
Por ejemplo, una función que conecte a MongoDB desde Python. Supongamos que queda así:
def conectar(host, puerto, usuario, palabra_secreta, nombre_base_de_datos)
Sé que no es tan larga, pero imaginemos que más adelante le agregamos más parámetros. Quedaría muy larga, mucho más. Así que mejor la definimos así:
def conectar(**opciones)
Veamos el código:
"""
Explicar **kwargs en Python con una
función
@author parzibyte
"""
def conectar(**opciones):
host = opciones["host"]
puerto = opciones["puerto"]
usuario = opciones["usuario"]
palabra_secreta = opciones["palabra_secreta"]
nombre_base_de_datos = opciones["nombre_base_de_datos"]
print(
"Host: {}, puerto: {}, usuario: {}, palabra_secreta: {}, nombre_base_de_datos: {}"
.format(host, puerto, usuario, palabra_secreta, nombre_base_de_datos))
En él indicamos que vamos a recibir una lista de argumentos con nombre con los dos asteriscos **, y en la función accedemos a ellos como cuando accedemos a un diccionario.
Ahora vemos la llamada, la cual puede ser primero definiendo un diccionario y pasándolo con **:
argumentos = {
"host": "127.0.0.1",
"puerto": "27017",
"usuario": "parzibyte",
"palabra_secreta": "hunter2",
"nombre_base_de_datos": "agenda",
}
conectar(**argumentos)
El código de arriba confunde un poco, porque tenemos que usar ** al llamar a la función, aparte de cuando la definimos. La otra opción que es más expresiva y legible es la siguiente:
conectar(
host="12.34.56.78",
puerto="27017",
usuario="parzibyte",
palabra_secreta="hunter2",
nombre_base_de_datos="agenda")
A mi parecer es más legible, y no hay necesidad de definir un diccionario. Además, el comportamiento dentro de la función es el mismo, al final es un diccionario que tiene claves y valores.
En caso de tener múltiples argumentos, pero que algunos sean opcionales
Ahora veamos otro uso clave de kwargs en Python. Esto va combinado con lo que vimos acerca de switch en Pyhton.
¿Qué pasa si algunos argumentos tienen valores por defecto o son opcionales? muy fácil, usamos al método get
de los diccionarios y proveemos un valor por defecto, algo así:
"""
Explicar **kwargs en Python con una
función
@author parzibyte
"""
def conectar(**opciones):
host = opciones.get("host", "localhost")
puerto = opciones.get("puerto", "27017")
usuario = opciones.get("usuario",
"root?") # Aunque root no existe en MongoDB, creo
palabra_secreta = opciones.get("palabra_secreta", "123")
nombre_base_de_datos = opciones.get("nombre_base_de_datos", "test")
print(
"Host: {}, puerto: {}, usuario: {}, palabra_secreta: {}, nombre_base_de_datos: {}"
.format(host, puerto, usuario, palabra_secreta, nombre_base_de_datos))
De esta manera, aunque no se proporcionen todos los datos (por ejemplo, pasar únicamente el usuario y la contraseña), no habrá errores:
conectar(usuario="parzibyte", palabra_secreta="hunter2")
En ese caso se tomarán los demás valores por defecto. Cabe mencionar que no tenemos que pasar a los argumentos en orden, lo de arriba igualmente podría ser así:
conectar(palabra_secreta="hunter2", usuario="parzibyte")
Esa es una de las ventajas de **kwargs en Python.
Diferencia entre **kwargs y *args en Python
Los **kwargs
tienen nombre, aunque estén desordenados, pero tienen un nombre. En cambio, *args
es una lista plana, no se sabe qué valor se encuentra en determinada posición.
Infiero que los keyword arguments son usados para definir valores por defecto en funciones que toman muchos de los mismos.
Usar argumentos, **kwargs y *args en Python en la misma función
Justo ahora no se me ocurre un uso, pero claro que se pueden usar en conjunto. Sólo recuerda estas reglas:
- Primero van los argumentos posicionales
- Luego, opcionalmente, va
*args
- Finalmente, va
**kwargs
Veamos este ejemplo:
"""
Explicar **kwargs en Python con una
función
@author parzibyte
"""
def conectar(nombre, *numeros, **opciones):
print("Hola, ", nombre)
for numero in numeros:
print(numero)
host = opciones.get("host", "localhost")
puerto = opciones.get("puerto", "27017")
usuario = opciones.get("usuario",
"root?") # Aunque root no existe en MongoDB, creo
palabra_secreta = opciones.get("palabra_secreta", "123")
nombre_base_de_datos = opciones.get("nombre_base_de_datos", "test")
print(
"Host: {}, puerto: {}, usuario: {}, palabra_secreta: {}, nombre_base_de_datos: {}"
.format(host, puerto, usuario, palabra_secreta, nombre_base_de_datos))
conectar("Luis", 1, 2, 3, 4, usuario="parzibyte", palabra_secreta="hunter2")
En ese caso el argumento posicional es nombre, pero podría haber más. Después definimos los argumentos indefinidos, y finalmente pasamos los **kwargs. Recuerda que en lugar de estos últimos, podríamos definir un diccionario y pasarlo con **diccionario.
¿Quieres probar el código? puedes descargarlo y ejecutarlo, pero primero instala el intérprete de Python.
Espero que haya quedado claro. Si tienes dudas, ahí están los comentarios.
Pingback: Conversión de texto a voz (TTS) con Python y gTTS - Parzibyte's blog
Pingback: Comprobar si diccionario tiene determinada clave en Python - Parzibyte's blog