Funciones en Python: *args, **kwargs, parámetros por defecto y keyword-only

Python ofrece una de las formas más flexibles de definir los parámetros de una función. Entender *args, **kwargs, los valores por defecto y los parámetros keyword-only te permite escribir funciones más expresivas y reutilizables, y evitar el bug silencioso más habitual con objetos mutables como valor por defecto.

*args: número variable de argumentos posicionales

*args recoge todos los argumentos posicionales extra en una tupla:

def suma(*args):
    return sum(args)

print(suma(1, 2, 3))         # 6
print(suma(10, 20, 30, 40))  # 100
print(suma())                # 0

# Combinar con parámetros normales
def saludo(mensaje, *nombres):
    for nombre in nombres:
        print(f"{mensaje}, {nombre}!")

saludo("Hola", "Ana", "Luis", "María")
# Hola, Ana!
# Hola, Luis!
# Hola, María!

**kwargs: número variable de argumentos nombrados

**kwargs recoge todos los argumentos de palabra clave extra en un diccionario:

def crear_usuario(**kwargs):
    campos_permitidos = {"nombre", "email", "rol"}
    for k in kwargs:
        if k not in campos_permitidos:
            raise ValueError(f"Campo no permitido: {k}")
    return kwargs

u = crear_usuario(nombre="Ana", email="[email protected]", rol="admin")
print(u)  # {'nombre': 'Ana', 'email': '[email protected]', 'rol': 'admin'}

El error clásico: mutables como valor por defecto

Los valores por defecto se evalúan una sola vez cuando se define la función, no cada vez que se llama. Si usas una lista o dict como valor por defecto, todas las llamadas comparten el mismo objeto:

# MAL: la lista se crea una sola vez
def agregar(item, lista=[]):
    lista.append(item)
    return lista

print(agregar("a"))   # ['a']
print(agregar("b"))   # ['a', 'b']  — ¡sorpresa!
print(agregar("c"))   # ['a', 'b', 'c']

# BIEN: usa None como sentinel
def agregar(item, lista=None):
    if lista is None:
        lista = []
    lista.append(item)
    return lista

print(agregar("a"))   # ['a']
print(agregar("b"))   # ['b']  — correcto

Keyword-only: parámetros que solo aceptan nombre

Los parámetros después de un * desnudo solo pueden pasarse por nombre, nunca por posición. Esto hace la API de la función más explícita:

def conectar(host, puerto, *, ssl=True, timeout=30):
    print(f"Conectando a {host}:{puerto} ssl={ssl} timeout={timeout}")

conectar("localhost", 5432)                    # OK
conectar("localhost", 5432, ssl=False)         # OK
# conectar("localhost", 5432, False)           # TypeError

Positional-only: parámetros solo por posición (Python 3.8+)

Los parámetros antes de / solo pueden pasarse por posición:

def dividir(numerador, denominador, /):
    return numerador / denominador

print(dividir(10, 2))           # 5.0
# dividir(numerador=10, denominador=2)  # TypeError

Orden correcto de parámetros

def f(pos_only, /, normal, *, kw_only):
    pass

# Combinación completa
def api(metodo, ruta, /, *, timeout=30, retries=3, **headers):
    pass

api("GET", "/api/v1/users", timeout=10, Authorization="Bearer xyz")

La regla: define los parámetros requeridos al principio, los opcionales con valor por defecto después, *args si necesitas número variable de posicionales y **kwargs al final si necesitas flexibilidad máxima. Evita siempre los mutables como valor por defecto.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP