▷ *args` y `**kwargs` en Python — Qué son y cuándo usarlos 2026

*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.

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, numeros es una tupla normal. Puedes iterarla, indexarla, pasarla a sum(), 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 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 args y kwargs pero 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.

Ver el curso completo →

35+ lecciones · Proyecto real · Acceso de por vida · 14 días de garantía

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicar un comentario