Python *args y **kwargs explicados: funciones flexibles con ejemplos claros

  1. Inicio
  2. Blog
  3. Desarrollo de software

Desarrollo de software

Python *args y **kwargs explicados: Funciones flexibles con ejemplos claros

Esas estrellas en firmas de funciones son menos misteriosas de lo que parecen. Aprenda cómo *args y **kwargs recopilan argumentos, cómo las mismas estrellas los descomprimen y cómo diseñar firmas de funciones que sean difíciles de usar incorrectamente.

8 min de lectura
Python *args y **kwargs explicados: funciones flexibles con ejemplos claros

Puntos clave

  • *args recopila argumentos posicionales adicionales en una tupla.
  • **kwargs recopila argumentos de palabras clave adicionales en un diccionario.
  • Los nombres son sólo una convención, ya que * y ** son ​​lo que importa.
  • Lo mismo protagoniza desempaquetar una lista o diccionario cuando llamas a una función.

Tarde o temprano, leerás una función como def handler(*args, **kwargs) y te preguntarás qué acepta. Python *args y **kwargs permiten que una función tome una cantidad flexible de argumentos. Impulsan decoradores, contenedores y muchas API de biblioteca.

Esta guía cubre cómo funcionan en ambas direcciones, junto con las formas más nuevas en que Python le permite controlar cómo se llama una función. Ejecutamos cada ejemplo en Python 3.13 y el resultado que se muestra proviene de esa ejecución.

¿Qué hace *args?

Un parámetro escrito con una estrella recopila cualquier argumento posicional adicional en una tupla:

def total(*args):
    print(type(args).__name__, args)
    return sum(args)

print(total(1, 2, 3))
# tuple (1, 2, 3)
# 6

print(total())
# tuple ()
# 0

Sin argumentos, simplemente obtienes una tupla vacía, por lo que la función sigue funcionando. El nombre args es una convención. *numbers se comportaría igual y, a menudo, un nombre más descriptivo es mejor.

¿Qué hace **kwargs?

Dos estrellas recopilan los argumentos de palabras clave adicionales en un diccionario:

def describe(**kwargs):
    print(type(kwargs).__name__, kwargs)

describe(name="Asha", city="Pune")
# dict {'name': 'Asha', 'city': 'Pune'}

Debido a que es un diccionario normal, puedes recorrerlo, buscar claves con get() y pasarlo. Las claves mantienen el orden en que fueron pasadas.

Orden de parámetros

Cuando mezclas tipos de parámetros, Python requiere un orden específico:

  1. Parámetros posicionales regulares
  2. *argumentos
  3. Parámetros de solo palabras clave
  4. **kwargs
def order(item, qty=1, *extras, priority=False, **notes):
    print(item, qty, extras, priority, notes)

order("pen")
# pen 1 () False {}

order("pen", 2, "gift wrap", "card", priority=True, note="leave at door")
# pen 2 ('gift wrap', 'card') True {'note': 'leave at door'}

Aviso priority. Debido a que viene después de *extras, sólo se puede configurar por nombre, por lo que un argumento posicional perdido nunca podría configurarlo por accidente. No se permite colocar **kwargs antes, y nuestra prueba generó un SyntaxError: ​​”los argumentos no pueden seguir el argumento de palabra clave var”.

Desempacar al llamar

Las mismas estrellas funcionan a la inversa en el lugar de la llamada. Una estrella distribuye una lista o tupla en argumentos posicionales y dos estrellas distribuyen un diccionario en argumentos de palabras clave:

nums = [4, 5, 6]
total(*nums)            # same as total(4, 5, 6) -> 15

settings = {"name": "Ben", "city": "Delhi"}
describe(**settings)    # same as describe(name="Ben", city="Delhi")

El desempaquetado funciona con cualquier función, no solo con aquellas que usan *args y **kwargs. Esto resulta útil cuando la configuración llega como un diccionario:

def connect(host, port, timeout=5):
    return f"{host}:{port} (timeout {timeout})"

connect(**{"host": "db.local", "port": 5432})
# 'db.local:5432 (timeout 5)'

connect(**{"host": "db.local", "port": 5432, "ssl": True})
# TypeError: connect() got an unexpected keyword argument 'ssl'

Ese error es útil. Una función sin **kwargs rechaza opciones desconocidas, por lo que un error tipográfico en el nombre de una configuración falla ruidosamente en lugar de ignorarse silenciosamente.

Argumentos de solo palabras clave

Un simple * en la firma, sin nombre, significa “todo lo que sigue a esto debe pasarse por palabra clave”:

def make_user(name, *, admin=False):
    return name, admin

make_user("Asha", admin=True)
# ('Asha', True)

make_user("Asha", True)
# TypeError: make_user() takes 1 positional argument but 2 were given

Esta es una excelente manera de proteger indicadores booleanos. Una llamada como make_user("Asha", True) obliga a los lectores a adivinar qué significa True. Requerir admin=True hace que cada llamada se explique por sí misma.

Argumentos solo posicionales

Desde Python 3.8, un / marca los parámetros que solo se pueden pasar por posición:

def area(width, height, /):
    return width * height

area(3, 4)                 # 12
area(width=3, height=4)
# TypeError: area() got some positional-only arguments
#   passed as keyword arguments: 'width, height'

Esto permite que una biblioteca cambie el nombre de esos parámetros más adelante sin romper el código de nadie, ya que nadie puede depender de los nombres. Varios integrados funcionan de esta manera. Por ejemplo, len(obj=[1]) no funciona.

Consejo: El patrón completo es def f(pos_only, /, normal, *, kw_only). Rara vez necesitará los tres, pero saber qué significan / y * le ayudará a leer la documentación y las firmas de la biblioteca.

Decoradores y envoltorios

Esta es la principal razón en el mundo real para utilizar ambos juntos. Un contenedor a menudo necesita aceptar cualquier argumento que tome la función original y pasarlo sin cambios:

import functools, time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def slow_add(a, b, delay=0.01):
    time.sleep(delay)
    return a + b

print(slow_add(2, 3, delay=0.02))
# slow_add took 0.0205s
# 5

El contenedor no necesita conocer los parámetros de slow_add en absoluto, por lo que funciona para cualquier función. Su cifra de tiempo diferirá ligeramente. Nuestra guía para decoradores de Python explica functools.wraps y otros detalles.

Opciones de fusión

** también funciona dentro de un literal de diccionario, lo que proporciona una forma sencilla de aplicar anulaciones además de los valores predeterminados:

defaults = {"theme": "light", "lang": "en"}

def build(**overrides):
    return {**defaults, **overrides}

build(theme="dark")
# {'theme': 'dark', 'lang': 'en'}

Las claves posteriores ganan, por lo que la anulación reemplaza la predeterminada. Las subclases también usan este patrón, como en super().__init__(*args, **kwargs), para pasar argumentos adicionales a una clase principal. Consulte clases y objetos de Python para obtener más información sobre super().

hoja de trucos

Sintaxis donde Significado
def f(*argumentos) Definición Reúna argumentos posicionales adicionales en una tupla
def f(**kwargs) Definición Recopile argumentos de palabras clave adicionales en un dictado
definición f(a, *, b) Definición b es solo para palabras clave
definición f(a, /, b) Definición a es solo posicional
f(*items) llamar Distribuir una secuencia en argumentos posicionales.
f(**options) llamar Difunda un dictado en argumentos de palabras clave
{**a, **b} literales Fusionar diccionarios
[*a, *b] literales Combina iterables en una lista

Cuando evitarlos

La flexibilidad tiene un precio. Una función definida como def create(**kwargs) no dice nada a los lectores sobre qué opciones existen y los editores no pueden autocompletarlas. Los errores tipográficos pueden pasar silenciosamente a través de varias capas antes de que algo falle.

Por lo tanto, prefiera parámetros explícitos cuando sepa lo que necesita una función. Utilice *args y **kwargs cuando realmente necesite flexibilidad: envoltorios, decoradores, reenvío de argumentos a otra función o una entrada verdadera de longitud variable como total(*numbers). Si una función necesita muchas opciones con nombre, un dataclass también puede ser más claro.

Errores comunes

Usando un valor predeterminado mutable

No se trata de estrellas, pero suele aparecer cerca de ellas. Un valor predeterminado se crea una vez, cuando se define la función, y luego se comparte entre llamadas:

def bad(items=[]):
    items.append(1)
    return items

print(bad(), bad())
# [1, 1] [1, 1]

Ambas llamadas arrojaron la misma lista, que para entonces contenía dos elementos. Utilice items=None y cree una nueva lista dentro de la función.

Otros errores

  • Olvidarse de desempacar. Llamar a total(nums) pasa una lista en lugar de tres números. Escribe total(*nums).
  • Tragar errores tipográficos. Una función que acepta **kwargs pero ignora claves desconocidas oculta opciones mal escritas. Valídalos o evítalos **kwargs.
  • Pasar el mismo argumento dos veces. f(1, **{"a": 2}), donde a es el primer parámetro, genera un error de “valores múltiples”.
  • No reenviar en un contenedor. Llamar a func(args, kwargs) pasa una tupla y un dict. La forma correcta es func(*args, **kwargs).

Escriba sugerencias para *args y **kwargs

Las sugerencias de tipo en *args y **kwargs describen cada valor individual, no la colección completa. Esto sorprende a mucha gente:

def total(*prices: float) -> float:
    return sum(prices)      # prices is tuple[float, ...]

def tag(**attrs: str) -> str:
    return " ".join(f'{k}="{v}"' for k, v in attrs.items())   # dict[str, str]

total(1.5, 2.5)                  # 4.0
tag(id="x", role="button")       # id="x" role="button"

Aquí prices se trata como una tupla de flotantes y attrs como un diccionario de cadenas. Cuando los argumentos de palabras clave tienen diferentes tipos, Python 3.12 agregó una opción precisa: un TypedDict combinado con Unpack.

from typing import TypedDict, Unpack

class Options(TypedDict, total=False):
    timeout: float
    retries: int

def fetch(url: str, **opts: Unpack[Options]) -> None: ...

Un verificador de tipos como mypy o Pyright puede entonces rechazar fetch(url, retries="3") o un nombre de opción mal escrito, lo que un simple **kwargs nunca puede hacer. Sin embargo, el comportamiento en tiempo de ejecución no cambia. Cuando ejecutamos fetch("u", retries="3") en Python 3.13, se ejecutó sin quejas, porque Python en sí no aplica sugerencias de tipo.

Si se encuentra documentando una larga lista de claves aceptadas para **kwargs, considere reemplazarlas con parámetros explícitos de solo palabras clave. Luego, los editores pueden autocompletarlos y los errores se detectan de inmediato.

Conclusión

Recuerda las dos direcciones. En una definición, las estrellas se acumulan. En una llamada, desempacan. Agregue argumentos de solo palabras clave para que las llamadas sean más claras y reserve **kwargs para el código que realmente reenvía argumentos. Para practicar, escriba un pequeño decorador que registre los argumentos de cada llamada y utilícelo en algunas funciones diferentes.

Preguntas frecuentes

¿Cuál es la diferencia entre *args y **kwargs?

*args recopila argumentos posicionales adicionales en una tupla. **kwargs recopila argumentos de palabras clave adicionales en un diccionario. Una función puede utilizar ambos juntos.

¿Tengo que llamarlos args y kwargs?

No. Las estrellas hacen el trabajo, por lo que *valores u **opciones son igualmente válidos. args y kwargs son simplemente una convención ampliamente reconocida.

¿En qué orden deben ir *args y **kwargs?

Primero los parámetros regulares, luego *args, luego los parámetros de solo palabras clave y **kwargs por último. Cualquier otro orden provoca un error de sintaxis.

¿Qué significa un solo * en la definición de una función?

Un * simple significa que cada parámetro posterior debe pasarse mediante palabra clave. Hace que las llamadas sean más claras, especialmente para indicadores booleanos.

¿Qué significa / en una función de Python?

Los parámetros antes de / son solo posicionales, por lo que quienes llaman no pueden pasarlos por nombre. Esta característica se agregó en Python 3.8.

sigue aprendiendo

Trabajar con Stack Quora

¿Necesitas ayuda con pruebas, WordPress o SEO?

Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.

Sigue leyendo

Más guías sobre testing, IA y desarrollo moderno

¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.

Inteligencia artificial

Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas

Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…

Leer artículo →
DevOps

Explicación de las variables de entorno: archivos .env, secretos y configuración bien hechos

Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…

Leer artículo →
Desarrollo de software

Explicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429

La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …

Leer artículo →
Automatización de pruebas

Burlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan

Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…

Leer artículo →
Pruebas de software

Explicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%

Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…

Leer artículo →
Bases de datos y SQL

DELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno

DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…

Leer artículo →
Bases de datos y SQL

Vistas SQL explicadas: cree, use y evite los errores, con ejemplos

Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…

Leer artículo →
Desarrollo de software

Interfaz TypeScript versus tipo: diferencias reales, probadas con el compilador

Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…

Leer artículo →
Desarrollo de software

Python *args y **kwargs explicados: funciones flexibles con ejemplos claros

Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…

Leer artículo →
Desarrollo de software

Funciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas

Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…

Leer artículo →
Inteligencia artificial

¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar

¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…

Leer artículo →
DevOps

Monitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos

Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…

Leer artículo →
DevOps

Docker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta

Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…

Leer artículo →
Automatización de pruebas

TDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una

TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …

Leer artículo →
Desarrollo de software

Git merge vs rebase: diferencias, ejemplos y cuándo usar cada uno

Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.

Leer artículo →
Desarrollo de software

List comprehension en Python: sintaxis, ejemplos y cuándo evitarla

List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.

Leer artículo →