Argumentos posicionales
En Python los parámetros de una función son posicionales por defecto, esto quiere decir que el orden en que le pasamos los argumentos es importante. Veamos un ejemplo:
def juntar_palabras(palabra1, palabra2):
return palabra1 + palabra2
print(juntar_palabras('perro', 'loco')) # Salida: 'perroloco'
Si hubieramos pasado primero ‘loco’ y luego ‘perro’ el resultado hubiera estado invertido, devolviendo ‘locoperro’.
¿Qué sucede si quiero concatenar 3 palabras? Una opción sería agregar un tercer parámetro a la función:
def juntar_palabras(palabra1, palabra2, palabra3):
return palabra1 + palabra2 + palabra3
print(juntar_palabras('perro', 'loco', 'lindo')) # Salida: 'perrolocolindo'
¿Y cuándo no sabemos cuántos argumentos va a recibir nuestra función?
Ahí es donde aparece la utilidad de *args
:
def juntar_palabras(*args):
salida = ''
for palabra in args:
salida += palabra
return salida
print(juntar_palabras('perro', 'loco', 'lindo', 'grande')) # Salida: 'perrolocolindogrande'
Como se observa en el ejemplo, utilizar *args
nos permite recibir una cantidad de argumentos indefinida y utilizarla como si estuvieras pasando una tupla con esos valores.
Argumentos con nombre
Una alternativa que nos permite Python es crear una función con parámetros que tengan nombre (o keyword arguments). De este modo, ya no importará el orden, sino que estaremos asignando un valor a cada variable que podrá ser usado dentro de la función:
def describir_pokemon(tipo, color, tamaño):
return f"El Pokemon es {tipo}, {color} y {tamaño}."
print(describir_pokemon(tamaño="pequeño", color="azul", tipo="agua"))
# Salida: 'El Pokemon es tipo agua, color azul y pequeño.'
En este caso, para recibir una cantidad variable de argumentos con nombre tenemos que usar no uno, sino dos asteriscos (**kwargs
):
def describir_atributos(**kwargs):
for clave, valor in kwargs.items():
print(f"{clave}: {valor}")
describir_atributos(tipo="roca", color="negro", tamaño="grande")
# Salida:
# tipo: roca
# color: negro
# tamaño: grande
Lo que terminamos obteniendo es un diccionario donde las claves son los nombres de los argumentos y los valores son los datos proporcionados.
La gran mentira sobre args y kwargs 😮
Dado que cuando queremos el comportamiento explicado previamente usamos *args
y **kwargs
, podríamos creer que se trata de palabras reservadas que tienen un valor particular en el lenguaje (como True, print o def) pero ¡nada más lejos de la realidad! *args
y **kwargs
son simples convenciones y podríamos obtener el mismo comportamiento con cualquier otro nombre mientras respetemos el patrón de un asterisco para argumentos posicionales y dos para argumentos con nombre:
def ejemplo_posic(*posicionales):
print("Posicionales:", posicionales)
ejemplo_posic(1, 2, 3)
# Salida:
# Posicionales: (1, 2, 3)
def ejemplo_con_nombre(**con_nombre):
print("Con nombre:", con_nombre)
ejemplo_con_nombre(atributo1="valor1", atributo2="valor2")
# Salida:
# Con nombre: {'atributo1': 'valor1', 'atributo2': 'valor2'}
Unamos lo aprendido 🤝
¿Qué pasa si en una misma función quiero recibir una cantidad desconocida de argumentos posicionales y además una cantidad desconocida de argumentos con nombre? Bueno, tranquilos porque eso es perfectamente válido. Por completitud les voy a dejar un ejemplo con todas las posibilidades, el cual solo es funcional a partir de la versión 3.8 de Python. Se puede definir una función con los siguientes tipos de parámetros en este orden específico:
Parámetros posicionales obligatorios (los “normales”).
/
: Para indicar que todos los parámetros antes de esta barra deben ser posicionales obligatorios.Parámetros posicionales o por nombre.
*
: Para indicar que todos los parámetros después de*
deben ser pasados por nombre (keyword-only). Notar que este es otro uso del *, del cuál todavía no habíamos hablado.*args
: Captura una cantidad variable de argumentos posicionales.Parámetros con nombre (keyword-only).
**kwargs
: Captura una cantidad variable de argumentos con nombre.
def ejemplo_completo(pos1, pos2, /, pos_o_nombre, *, nombre1, nombre2="por defecto", **kwargs):
print("Posicionales obligatorios:")
print(f" pos1: {pos1}")
print(f" pos2: {pos2}")
print("\nPosicionales o por nombre:")
print(f" pos_o_nombre: {pos_o_nombre}")
print("\nSolo por nombre:")
print(f" nombre1: {nombre1}")
print(f" nombre2: {nombre2}")
print("\nArgumentos adicionales (**kwargs):")
for clave, valor in kwargs.items():
print(f" {clave}: {valor}")
# Llamada a la función
ejemplo_completo(1, 2, 3, nombre1="valor1", extra1="adicional1", extra2="adicional2")
Posicionales obligatorios:
pos1: 1
pos2: 2
Posicionales o por nombre:
pos_o_nombre: 3
Solo por nombre:
nombre1: valor1
nombre2: por defecto
Argumentos adicionales (**kwargs):
extra1: adicional1
extra2: adicional2
Una aclaración más es que no es obligatorio pasar “de a uno” los argumentos posicionales y con nombre sino que podemos directamente pasarle la colección, siempre y cuando los desempaquemos con *
y **
:
mi_lista = [1, 2, 3]
mi_diccionario = {"a":4, "b":5}
def ejemplo_colecciones(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
return args, kwargs # Devuelve args y kwargs como una tupla
print(ejemplo_colecciones(*mi_lista, **mi_diccionario))
# Salida:
# args: (1, 2, 3)
# kwargs: {'a': 4, 'b': 5'}
# ((1, 2, 3), {'a': 4, 'b': 5})
Usando * y **
fuera de una función
Una de las cosas que también suele confundir un poco sobre este tema es que el comportamiento de *
y **
cambia fuera de funciones porque en este caso se utilizan para desempaquetar colecciones en otros contextos, tales como la creación de listas, tuplas o diccionarios. En particular *
se utiliza para desempaquetar (unpacking) colecciones como listas, sets y tuplas:
# Desempaquetar listas
lista1 = [1, 2, 3]
lista2 = [4, 5, 6]
combinada = [*lista1, *lista2] # [1, 2, 3, 4, 5, 6]
# Desempaquetar sets
set1 = {1, 2}
set2 = {3, 4}
combinado = {*set1, *set2}
print(combinado) # Salida: {1, 2, 3, 4}
Mientras que **
Se usa para desempaquetar diccionarios, permitiendo por ejemplo combinar sus claves y valores en un nuevo diccionario:
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
combinado = {**dict1, **dict2}
print(combinado) # Salida: {'a': 1, 'b': 2, 'c': 3, 'd': 4}
Resumiendo, en la definición de funciones *
y **
, se usan para recolectar entradas dinámicas, mientras que fuera de las funciones, se usan para expandir estructuras en nuevas colecciones.
Conclusiones y resumen
En Python *args
, y **kwargs
se usan para definir funciones de manera flexible. Además, existe el concepto de desempaquetado de colecciones con *
y **
fuera de las funciones, el cual nos permite expandir estructuras en nuevas colecciones.
¿Te interesa seguir aprendiendo más sobre Python?
Si este contenido te resultó útil y te interesa leer contenido sobre programación principalmente en Python, ciencia de datos e inteligencia artificial? Seguime en mis redes:
🐙 Pueden ver todo el código en un Jupyter notebook en Github
📘 LinkedIn donde comparto contenido con frecuencia: Gustavo Juantorena
📹 YouTube: Gustavo Juantorena - Programación y Ciencia de Datos