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.
