Saltar al contenido
🐍 Curso de Python
/

Módulo 1 · Lección 36 de 59

Argumentos variables: *args y **kwargs

Cómo escribir funciones que aceptan un número indeterminado de argumentos y cómo desempaquetar listas y diccionarios al llamar.

⏱ 13 min de lectura

print acepta un argumento, o cinco, o veinte. max funciona con dos números y con diez. Ninguna de las dos te obliga a decir cuántos vas a pasar antes de empezar.

Eso no es magia interna del lenguaje: es una característica que puedes usar en tus propias funciones con un asterisco.

*args: un número indeterminado de posicionales#

Pon un asterisco delante de un parámetro y ese parámetro recogerá todos los argumentos posicionales sobrantes, empaquetados en una tupla:

python
def sumar_gastos(*importes):
        print(importes)
    return sum(importes)


print(sumar_gastos(12.5, 30, 4.99))
print(sumar_gastos())
Salida en pantalla
(12.5, 30, 4.99)
47.49
()
0

Dentro de la función, importes es una tupla normal y corriente: puedes recorrerla con un for, medirla con len, pasarla a sum o rebanarla. Si no se pasa ningún argumento, es una tupla vacía, no un error.

Un parámetro con asterisco puede convivir con parámetros normales, siempre que estos vayan delante:

python
def calcular_factura(cliente, *importes):
        total = sum(importes)
    print(f"{cliente}: {len(importes)} líneas, total {total:.2f} euros")


calcular_factura("Ana", 19.99, 49.50, 7.25)
calcular_factura("Luis")
Salida en pantalla
Ana: 3 líneas, total 76.74 euros
Luis: 0 líneas, total 0.00 euros

**kwargs: un número indeterminado por nombre#

Con dos asteriscos, el parámetro recoge todos los argumentos por nombre que no encajen en ningún otro sitio, y los guarda en un diccionario donde la clave es el nombre:

python
def describir_producto(nombre, **caracteristicas):
        print(f"Producto: {nombre}")
    for clave, valor in caracteristicas.items():
                print(f"  {clave}: {valor}")


describir_producto("Camiseta", talla="M", color="azul", algodon=True)
Salida en pantalla
Producto: Camiseta
  talla: M
  color: azul
  algodon: True

Igual que antes, kwargs es una convención; lo que importa son los dos asteriscos. El nombre viene de keyword arguments, argumentos por palabra clave.

Las claves del diccionario son cadenas, así que dentro de la función se consultan como en cualquier diccionario: caracteristicas["talla"] o, más seguro, caracteristicas.get("talla", "sin especificar").

Los dos a la vez#

Se pueden combinar, y el orden en la firma es fijo: primero los parámetros normales, después *args, después **kwargs.

python
def registrar(*args, **kwargs):
        print("Posicionales:", args)
    print("Por nombre:  ", kwargs)


registrar(1, 2, 3, ciudad="Bilbao", urgente=True)
registrar()
Salida en pantalla
Posicionales: (1, 2, 3)
Por nombre:   {'ciudad': 'Bilbao', 'urgente': True}
Posicionales: ()
Por nombre:   {}

Una función con (*args, **kwargs) acepta literalmente cualquier llamada sin fallar nunca por número de argumentos.

En la firmaRecogeTipo dentro de la función
parametroUn argumento, por posición o por nombreEl valor que sea
parametro=valorLo mismo, pero opcionalEl valor que sea
*argsTodos los posicionales sobrantesTupla
**kwargsTodos los que van por nombre y sobranDiccionario

Desempaquetar al llamar#

El asterisco tiene un segundo uso, en el otro lado: en la llamada. Ahí no empaqueta, sino que hace lo contrario, repartir el contenido de una colección entre los parámetros.

python
def mostrar_linea(producto, cantidad, precio):
        print(f"{cantidad} x {producto} = {cantidad * precio:.2f} euros")


datos = ["Camiseta", 2, 19.99]
mostrar_linea(*datos)

pedido = {"producto": "Zapatillas", "cantidad": 1, "precio": 49.5}
mostrar_linea(**pedido)
Salida en pantalla
2 x Camiseta = 39.98 euros
1 x Zapatillas = 49.50 euros

mostrar_linea(*datos) equivale a escribir mostrar_linea("Camiseta", 2, 19.99), y mostrar_linea(**pedido) equivale a mostrar_linea(producto="Zapatillas", cantidad=1, precio=49.5). Con el diccionario, las claves tienen que coincidir exactamente con los nombres de los parámetros, o saltará el TypeError de argumento inesperado que ya conoces.

Esto es especialmente útil con las listas de diccionarios que viste en su lección, donde cada diccionario representa un registro con las mismas claves.

Parámetros que solo se pueden pasar por nombre#

Hay un tercer uso del asterisco: si pones uno suelto en la firma, todo lo que venga detrás solo se podrá pasar por nombre.

python
def enviar_paquete(ciudad, *, urgente=False, asegurado=False):
        print(f"{ciudad} | urgente={urgente} | asegurado={asegurado}")


enviar_paquete("Sevilla", urgente=True)
enviar_paquete("Sevilla", True)
Salida en pantalla
Sevilla | urgente=True | asegurado=False
Traceback (most recent call last):
  File "/Users/erwin/curso-python/envios.py", line 6, in <module>
    enviar_paquete("Sevilla", True)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
TypeError: enviar_paquete() takes 1 positional argument but 2 were given

Parece una restricción molesta, pero es una decisión de diseño muy sensata: te obliga a que las llamadas a tu función sean legibles. Con esa firma, nadie podrá escribir en tu código enviar_paquete("Sevilla", True, False), que no se entiende sin mirar la definición. Es la técnica que usa la biblioteca estándar en funciones con muchas opciones, y es el motivo por el que sorted(lista, reverse=True) no se puede escribir sorted(lista, True).

Un ejemplo completo#

Juntamos todo en algo que podrías tener en un programa de verdad: una función que compone un mensaje con un número indeterminado de líneas y unas cuantas opciones de formato.

python
def componer_mensaje(destinatario, *lineas, firma="El equipo", mayusculas=False):
        cuerpo = "\n".join(lineas)
    mensaje = f"Hola, {destinatario}:\n{cuerpo}\n\n{firma}"
    if mayusculas:
                mensaje = mensaje.upper()
    return mensaje


print(componer_mensaje(
    "Ana",
    "Tu pedido ya está preparado.",
    "Puedes recogerlo a partir del martes.",
    firma="Tienda La Esquina",
))
Salida en pantalla
Hola, Ana:
Tu pedido ya está preparado.
Puedes recogerlo a partir del martes.

Tienda La Esquina

Fíjate en un detalle importante de esa firma: como *lineas se come todos los posicionales, firma y mayusculas solo se pueden pasar por nombre, aunque no haya ningún * suelto. El *lineas ya cumple esa función.