*args` y `**kwargs` en Python — Qué son y cuándo usarlos
Tarde o temprano abres un fichero de Python ajeno y te encuentras con esto:
def algo(*args, **kwargs):
...
Y la primera reacción es: “¿qué demonios significan ese asterisco y ese doble asterisco?”. Y peor — cuando los buscas en Google ves explicaciones tipo “argumentos variádicos posicionales y de palabra clave” y te quedas igual o peor.
Tranquilidad. *args y **kwargs son una herramienta concreta con una utilidad concreta: permitir que tu función acepte un número arbitrario de argumentos sin tener que enumerarlos uno a uno. Una vez los entiendes, los usas dos o tres veces al año (decoradores, wrappers, funciones genéricas) y dejas de pelearte con ellos.
En esta entrada te enseño lo que importa: qué hacen, cuándo usarlos, cómo leerlos cuando los ves en código ajeno y los errores típicos que comete todo el mundo al empezar.
Contenido
- 1 Lo importante: args y kwargs son nombres de convención, no palabras reservadas
- 2 *args — Argumentos posicionales variables
- 3 **kwargs — Argumentos por nombre variables
- 4 Desempaquetar con * y ** al llamar la función
- 5 Cuándo usarlos de verdad (y cuándo no)
- 6 Argumentos keyword-only (bonus avanzado)
- 7 Errores típicos al empezar
- 8 Resumen
- 9 Tu siguiente paso
Lo importante: args y kwargs son nombres de convención, no palabras reservadas
Empezamos por el malentendido más común. Mira esto:
def saludar(*nombres):
for nombre in nombres:
print(f"Hola, {nombre}")
saludar("Ana", "Luis", "Marta")
# Hola, Ana
# Hola, Luis
# Hola, Marta
Aquí no hay ningún args. Lo importante es el asterisco, no el nombre. *nombres y *args hacen exactamente lo mismo: agrupar todos los argumentos posicionales sobrantes en una tupla.
Por convención (y porque queda más legible) usamos *args y **kwargs cuando la función no tiene un sentido específico para esos argumentos — pasa cualquier cosa que se le mande. Pero si sí tienen sentido, ponles un nombre descriptivo:
def total_compra(usuario, *productos, **descuentos):
...
Mucho más legible que def total_compra(usuario, *args, **kwargs).
*args — Argumentos posicionales variables
Caso típico: quieres una función que sume números, pero no sabes cuántos te van a pasar.
def sumar(*numeros):
total = 0
for n in numeros:
total += n
return total
print(sumar(1, 2, 3)) # 6
print(sumar(1, 2, 3, 4, 5)) # 15
print(sumar()) # 0
*numeros recoge todos los argumentos posicionales sueltos en una tupla. Da igual si pasas 0, 3 o 100.
💡 Ojo: dentro de la función,
numeroses una tupla normal. Puedes iterarla, indexarla, pasarla asum(), lo que sea.
def sumar(*numeros):
return sum(numeros)
Combinarlo con argumentos normales
Puedes mezclar argumentos posicionales fijos con *args. Los fijos van antes:
def crear_factura(cliente, *productos):
print(f"Factura para: {cliente}")
for p in productos:
print(f" - {p}")
crear_factura("Ana", "Camiseta", "Pantalón", "Gorra")
# Factura para: Ana
# - Camiseta
# - Pantalón
# - Gorra
Si intentas poner argumentos posicionales después de *args, Python solo los acepta si los pasas por nombre — pero esto es un caso avanzado que casi nunca usas (se llaman keyword-only arguments, lo veremos más abajo).
**kwargs — Argumentos por nombre variables
Lo mismo que *args, pero para argumentos pasados con nombre=valor. En vez de una tupla, recoge un diccionario.
def crear_perfil(**datos):
for clave, valor in datos.items():
print(f"{clave}: {valor}")
crear_perfil(nombre="Ana", edad=30, ciudad="Madrid")
# nombre: Ana
# edad: 30
# ciudad: Madrid
Dentro de la función, datos es un dict normal: datos["nombre"], datos.get("edad"), etc.
Combinarlo con argumentos normales y con *args
El orden en la firma siempre es: posicionales fijos → *args → keyword-only → **kwargs.
def evento(titulo, *invitados, lugar="online", **extras):
print(f"Evento: {titulo} en {lugar}")
print(f"Invitados: {', '.join(invitados)}")
if extras:
print(f"Extras: {extras}")
evento("Cumple", "Ana", "Luis", lugar="casa", musica="reggaetón", picoteo=True)
# Evento: Cumple en casa
# Invitados: Ana, Luis
# Extras: {'musica': 'reggaetón', 'picoteo': True}
Si te lías con el orden, recuerda esta regla:
Lo concreto antes que lo genérico, posicional antes que nombrado.
📥 Llévate el cheatsheet de Python (gratis)
PDF de 6 páginas con lo esencial: tipos, condicionales, bucles, estructuras de datos, funciones y los errores que más vas a cometer. Para tener al lado mientras programas.
Sin spam. Te apuntas a la lista, descargas el cheatsheet y recibes contenido de Python cada semana.
Desempaquetar con * y ** al llamar la función
Aquí viene la otra cara de la moneda y es donde mucha gente se confunde. El asterisco también se usa al llamar una función, pero el sentido es el contrario: en vez de empaquetar, desempaqueta.
def sumar(a, b, c):
return a + b + c
valores = [1, 2, 3]
print(sumar(*valores)) # 6 → desempaqueta la lista en argumentos posicionales
datos = {"a": 1, "b": 2, "c": 3}
print(sumar(**datos)) # 6 → desempaqueta el dict en kwargs
Misma sintaxis, sentido invertido:
| Contexto | * hace |
** hace |
|---|---|---|
| Definir función | Empaqueta args sueltos en tupla | Empaqueta kwargs en dict |
| Llamar función | Desempaqueta iterable en posicionales | Desempaqueta dict en kwargs |
Saber leer esto te desbloquea código tipo:
def saludar(nombre, edad):
print(f"{nombre}, {edad} años")
datos = {"nombre": "Ana", "edad": 30}
saludar(**datos) # Ana, 30 años
Es como decir “expande este diccionario y úsalo como si hubieras escrito nombre="Ana", edad=30“.
Cuándo usarlos de verdad (y cuándo no)
Te hago la lista de casos reales donde sí tiene sentido, y otra de cuándo mejor no.
Casos donde sí
1. Wrappers / decoradores genéricos. Cuando escribes una función que envuelve a otra y no sabes qué argumentos recibe:
def medir_tiempo(funcion):
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = funcion(*args, **kwargs)
print(f"Tardó {time.time() - inicio:.4f}s")
return resultado
return wrapper
Aquí *args, **kwargs dentro del wrapper acepta cualquier firma. Y al llamar funcion(*args, **kwargs) los reenvía tal cual. Es el patrón estándar.
💡 ¿Quieres ver decoradores a fondo? Tengo una entrada completa: decoradores en Python explicados.
2. Funciones que reciben un número variable de cosas del mismo tipo. El típico print() o max():
def primer_no_vacio(*opciones):
for o in opciones:
if o:
return o
return None
primer_no_vacio(None, "", 0, "hola") # "hola"
3. Pasar configuración opcional sin escribir 20 parámetros. Útil pero peligroso (lo veremos en errores):
def conectar_db(**config):
host = config.get("host", "localhost")
port = config.get("port", 5432)
...
Casos donde NO
1. Cuando los argumentos están claros. No escribas:
def sumar(*args): # ❌ ¿qué espera realmente?
return sum(args)
Si siempre son dos números:
def sumar(a, b): # ✅ explícito y autodocumentado
return a + b
2. Cuando los arguments están definidos y son finitos. Usar **kwargs para “ahorrarte” escribir parámetros con nombre mata el autocompletado del IDE, oculta la API real de la función y te obliga a leer el cuerpo para entenderla. No mola.
3. Cuando alguien necesita saber qué keys son válidas. Si **kwargs tiene 5 claves esperadas, mejor declaras 5 parámetros con valores por defecto. El usuario lo verá en la firma sin abrir el código.
Argumentos keyword-only (bonus avanzado)
Algo que casi nadie sabe: si pones * solo (sin nombre) en la firma, los argumentos que vengan después solo se pueden pasar por nombre. Útil cuando quieres forzar legibilidad.
def pagar(monto, *, moneda="EUR", incluir_iva=True):
...
pagar(100) # ✓
pagar(100, moneda="USD") # ✓
pagar(100, "USD") # ❌ TypeError: takes 1 positional argument
Esto es muy útil para flags booleanos. Una llamada como pagar(100, True) no se entiende. pagar(100, incluir_iva=True) sí. Forzando keyword-only impides el primer caso.
Errores típicos al empezar
# 1. Olvidar el asterisco al desempaquetar
def saludar(a, b, c):
print(a, b, c)
datos = [1, 2, 3]
saludar(datos) # ❌ TypeError: missing 2 required positional arguments
saludar(*datos) # ✓ → 1 2 3
# 2. Confundir orden en la firma
def malo(**kwargs, *args): # ❌ SyntaxError: **kwargs siempre va al final
...
def bien(*args, **kwargs): # ✓
...
# 3. Usar **kwargs para args que son fijos
def crear_usuario(**datos): # ❌ qué espera?
nombre = datos["nombre"]
email = datos["email"]
...
def crear_usuario(nombre, email, telefono=None): # ✓ autodocumentado
...
# 4. Modificar args dentro de la función creyendo que afecta fuera
def add(*lista):
lista.append(99) # ❌ AttributeError: tuple has no attribute 'append'
# args es siempre TUPLA. Si quieres mutarla, conviértela:
def add(*lista):
lista = list(lista)
lista.append(99)
return lista
⚡ Tip-friki: los nombres son
argsykwargspero PEP 8 los escribe en minúsculas y sin guion bajo. Si ves**_kwargs, suele ser convención para “sé que lo recibo pero no lo voy a usar” — pasarlo a un wrapper sin estorbar.
Resumen
| Sintaxis | Lugar | Hace |
|---|---|---|
*args |
Definición | Empaqueta posicionales sobrantes en una tupla |
**kwargs |
Definición | Empaqueta nombrados sobrantes en un dict |
*lista |
Llamada | Desempaqueta iterable como argumentos posicionales |
**dict |
Llamada | Desempaqueta diccionario como argumentos por nombre |
* solo |
Definición | Lo que viene después es keyword-only |
| Orden firma | — | pos → *args → keyword-only → **kwargs |
¿Te ha valido esto?
Si te ha resultado útil, llévate el cheatsheet de Python en PDF — 6 páginas con tipos, condicionales, bucles, estructuras de datos, funciones y los errores típicos. Para tener al lado mientras programas. Gratis.
Sin spam. Email + cheatsheet + un correo por semana con tutoriales nuevos.
Tu siguiente paso
Si has llegado hasta aquí, ya no te van a sorprender. La próxima vez que veas def algo(*args, **kwargs): no es magia — es alguien escribiendo un wrapper genérico o pasando configuración opcional. Y cuando un decorador te pida *args, **kwargs en el wrapper, sabes exactamente por qué.
Si quieres aprender funciones, decoradores, clases y todo Python desde la base con proyectos reales que no se quedan en juguete, en El Pythonista lo enseño paso a paso.
Un abrazo,
Oscar
¿Quieres aprender Python en orden, no a saltos?
Esto que has leído es solo una pieza. En El Pythonista lo verás todo encadenado: 11 módulos, 35+ lecciones, código revisado, ejercicios y un proyecto real (MovieTracker) que crece contigo desde la primera variable hasta el deploy a producción.
35+ lecciones · Proyecto real · Acceso de por vida · 14 días de garantía
