Python *args y **kwargs Explicados: La Guía Completa
Updated on
Todo desarrollador de Python se encuentra con este muro en algún momento. Abres el código fuente de una librería, un pull request de un colega, o un proyecto open source, y ves firmas de funciones llenas de *args y **kwargs. ¿Qué significan los asteriscos? ¿Por qué hay uno y dos de ellos? ¿Cuándo deberías usar cuál? Hacerlo mal produce confusos mensajes de TypeError como "takes 2 positional arguments but 5 were given" o "got an unexpected keyword argument," y de repente una llamada a función sencilla se convierte en una sesión de depuración.
El problema empeora cuando necesitas escribir funciones flexibles tú mismo. Codificar cada parámetro hace que tu API sea rígida. Aceptar demasiados parámetros nombrados hace que la firma de la función sea ilegible. Necesitas una forma de escribir funciones que acepten un número variable de argumentos sin sacrificar claridad.
Python resuelve esto con dos características sintácticas especiales: *args para argumentos posicionales variables y **kwargs para argumentos de palabra clave variables. Esta guía explica ambos desde los fundamentos, cubre los operadores de desempaquetado, recorre patrones del mundo real y te ayuda a evitar los errores más comunes.
¿Qué son *args y **kwargs?
En Python, *args y **kwargs son convenciones para aceptar un número variable de argumentos en las definiciones de funciones.
*argsrecolecta argumentos posicionales extra en una tupla.**kwargsrecolecta argumentos de palabra clave extra en un diccionario.
Los nombres args y kwargs son convenciones, no requisitos. La magia viene de los prefijos * y **, no de los nombres en sí. Podrías escribir *values y **options y funcionarían idénticamente.
Aquí está la demostración más simple:
def show_args(*args, **kwargs):
print(f"args = {args}")
print(f"kwargs = {kwargs}")
show_args(1, 2, 3, name="Alice", age=30)
# args = (1, 2, 3)
# kwargs = {'name': 'Alice', 'age': 30}Los argumentos posicionales (valores pasados sin nombres) van a parar a args como una tupla. Los argumentos de palabra clave (valores pasados con sintaxis key=value) van a parar a kwargs como un diccionario. Ese es todo el concepto central.
Entendiendo *args: Argumentos Posicionales Variables
El asterisco simple * antes del nombre de un parámetro le dice a Python que empaquete todos los argumentos posicionales restantes en una tupla. Esto permite que tu función acepte cualquier número de valores posicionales.
Sintaxis Básica y Uso
def add_all(*args):
"""Sum any number of values."""
total = 0
for num in args:
total += num
return total
print(add_all(1, 2)) # 3
print(add_all(1, 2, 3, 4, 5)) # 15
print(add_all(10)) # 10
print(add_all()) # 0Dentro de la función, args es una tupla regular de Python. Puedes iterar sobre ella, acceder por índice, verificar su longitud y pasarla a otras funciones.
def describe_args(*args):
print(f"Type: {type(args)}")
print(f"Length: {len(args)}")
print(f"First element: {args[0] if args else 'N/A'}")
print(f"Contents: {args}")
describe_args("hello", 42, True)
# Type: <class 'tuple'>
# Length: 3
# First element: hello
# Contents: ('hello', 42, True)Combinando Parámetros Regulares con *args
Puedes mezclar parámetros estándar con *args. Todos los parámetros posicionales regulares se llenan primero, y cualquier argumento posicional restante va a *args:
def log_message(level, *args):
"""Log a message with a severity level."""
message = " ".join(str(a) for a in args)
print(f"[{level.upper()}] {message}")
log_message("info", "Server started on port", 8080)
# [INFO] Server started on port 8080
log_message("error", "Connection failed:", "timeout after", 30, "seconds")
# [ERROR] Connection failed: timeout after 30 secondsEjemplo del Mundo Real: Una Función Promedio Flexible
def average(*values):
"""Calculate the average of any number of values."""
if not values:
raise ValueError("average() requires at least one argument")
return sum(values) / len(values)
print(average(85, 90, 78)) # 84.33333333333333
print(average(100)) # 100.0
print(average(72, 88, 95, 67, 91)) # 82.6Ejemplo del Mundo Real: Ayudante de Formato de Cadenas
def build_path(*segments):
"""Join path segments with forward slashes, stripping extras."""
cleaned = [seg.strip("/") for seg in segments if seg]
return "/" + "/".join(cleaned)
print(build_path("api", "v2", "users", "123"))
# /api/v2/users/123
print(build_path("/data/", "/reports/", "2026/", "sales.csv"))
# /data/reports/2026/sales.csvEntendiendo **kwargs: Argumentos de Palabra Clave Variables
El doble asterisco ** antes del nombre de un parámetro le dice a Python que empaquete todos los argumentos de palabra clave restantes en un diccionario. Esto permite que tu función acepte cualquier número de valores nombrados.
Sintaxis Básica y Uso
def print_info(**kwargs):
"""Print key-value pairs in a formatted way."""
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="Seattle")
# name: Alice
# age: 30
# city: SeattleDentro de la función, kwargs es un diccionario estándar de Python. Puedes usar .get(), .keys(), .values(), .items() y cualquier otro método de diccionario.
def describe_kwargs(**kwargs):
print(f"Type: {type(kwargs)}")
print(f"Keys: {list(kwargs.keys())}")
print(f"Values: {list(kwargs.values())}")
describe_kwargs(x=10, y=20, z=30)
# Type: <class 'dict'>
# Keys: ['x', 'y', 'z']
# Values: [10, 20, 30]Ejemplo del Mundo Real: Constructor de Configuración
def create_connection(host, port, **kwargs):
"""Create a database connection with optional settings."""
config = {
"host": host,
"port": port,
"timeout": kwargs.get("timeout", 30),
"retries": kwargs.get("retries", 3),
"ssl": kwargs.get("ssl", True),
"pool_size": kwargs.get("pool_size", 5),
}
# Add any extra settings the caller provided
for key, value in kwargs.items():
if key not in config:
config[key] = value
return config
# Basic usage
basic = create_connection("localhost", 5432)
print(basic)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'retries': 3, 'ssl': True, 'pool_size': 5}
# With custom options
custom = create_connection(
"db.example.com", 5432,
timeout=60,
ssl=False,
application_name="my_app"
)
print(custom)
# {'host': 'db.example.com', 'port': 5432, 'timeout': 60, 'retries': 3, 'ssl': False, 'pool_size': 5, 'application_name': 'my_app'}Ejemplo del Mundo Real: Constructor de Etiquetas HTML
def html_tag(tag, content="", **attributes):
"""Generate an HTML tag with optional attributes."""
attr_str = ""
for key, value in attributes.items():
# Convert Python naming to HTML (class_ -> class)
html_key = key.rstrip("_")
attr_str += f' {html_key}="{value}"'
if content:
return f"<{tag}{attr_str}>{content}</{tag}>"
return f"<{tag}{attr_str} />"
print(html_tag("a", "Click here", href="https://example.com", class_="btn"))
# <a href="https://example.com" class="btn">Click here</a>
print(html_tag("img", src="photo.jpg", alt="A photo", width="200"))
# <img src="photo.jpg" alt="A photo" width="200" />
print(html_tag("p", "Hello world", id="intro", style="color: blue"))
# <p id="intro" style="color: blue">Hello world</p>Usando *args y **kwargs Juntos
Puedes usar ambos en la misma definición de función para aceptar cualquier combinación de argumentos posicionales y de palabra clave. El orden de los parámetros sigue reglas estrictas.
Reglas de Ordenamiento de Parámetros
Python impone este orden exacto en las firmas de funciones:
- Parámetros posicionales regulares
*args(posicional variable)- Parámetros solo-palabra-clave (después de
*args) **kwargs(palabra clave variable)
def example(a, b, *args, option=True, **kwargs):
print(f"a = {a}")
print(f"b = {b}")
print(f"args = {args}")
print(f"option = {option}")
print(f"kwargs = {kwargs}")
example(1, 2, 3, 4, 5, option=False, color="red", size=10)
# a = 1
# b = 2
# args = (3, 4, 5)
# option = False
# kwargs = {'color': 'red', 'size': 10}Aquí hay un resumen de los tipos de parámetros y su orden:
| Posición | Tipo | Sintaxis | Ejemplo | Descripción |
|---|---|---|---|---|
| 1ro | Posicional | param | a, b | Requerido, llenado por posición |
| 2do | Por defecto | param=value | c=10 | Opcional, llenado por posición o nombre |
| 3ro | Posicional variable | *args | *args | Recolecta argumentos posicionales extra |
| 4to | Solo-palabra-clave | param (después de *) | option=True | Debe pasarse por nombre |
| 5to | Palabra clave variable | **kwargs | **kwargs | Recolecta argumentos de palabra clave extra |
Patrón Común: Función de Paso a Través
Uno de los patrones más útiles con *args y **kwargs es crear funciones que pasen todos los argumentos a otra función:
def timed_call(func, *args, **kwargs):
"""Call a function and measure its execution time."""
import time
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
def expensive_sum(a, b, c):
import time
time.sleep(0.1)
return a + b + c
result = timed_call(expensive_sum, 10, 20, c=30)
# expensive_sum took 0.1003s
print(result)
# 60Desempaquetado con * y **
Los operadores * y ** funcionan en ambas direcciones: empaquetan argumentos en las definiciones de funciones, y desempaquetan argumentos en las llamadas a funciones y otros contextos.
Desempaquetando Listas y Tuplas con *
Usa * para desempaquetar un iterable en argumentos posicionales individuales:
def add(a, b, c):
return a + b + c
numbers = [10, 20, 30]
# Without unpacking - this causes TypeError
# add(numbers) # TypeError: add() missing 2 required positional arguments
# With unpacking - spreads list into separate arguments
result = add(*numbers)
print(result) # 60
# Works with tuples, sets, and any iterable
coords = (5, 10, 15)
print(add(*coords)) # 30También puedes usar * para desempaquetar en asignaciones y construcción de listas (Python 3.5+):
# Extended unpacking in assignments
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# Unpacking in list/tuple construction
list_a = [1, 2, 3]
list_b = [4, 5, 6]
combined = [*list_a, *list_b]
print(combined) # [1, 2, 3, 4, 5, 6]
# Unpacking with additional elements
extended = [0, *list_a, 99, *list_b, 100]
print(extended) # [0, 1, 2, 3, 99, 4, 5, 6, 100]Desempaquetando Diccionarios con **
Usa ** para desempaquetar un diccionario en argumentos de palabra clave:
def create_user(name, email, role="viewer"):
return {"name": name, "email": email, "role": role}
user_data = {"name": "Alice", "email": "alice@example.com", "role": "admin"}
# Unpack dict into keyword arguments
user = create_user(**user_data)
print(user)
# {'name': 'Alice', 'email': 'alice@example.com', 'role': 'admin'}Fusionando Diccionarios con **
Uno de los usos más prácticos de ** es fusionar diccionarios:
defaults = {"color": "blue", "size": 12, "font": "Arial"}
user_prefs = {"color": "red", "size": 16}
# Merge: user_prefs override defaults
merged = {**defaults, **user_prefs}
print(merged)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# Python 3.9+ also supports the | operator
merged_new = defaults | user_prefs
print(merged_new)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# Adding extra keys during merge
final = {**defaults, **user_prefs, "theme": "dark"}
print(final)
# {'color': 'red', 'size': 16, 'font': 'Arial', 'theme': 'dark'}Combinando Desempaquetado * y **
Puedes usar ambos operadores juntos al llamar a una función:
def report(title, *items, separator="---", **metadata):
print(f"== {title} ==")
for item in items:
print(f" - {item}")
print(separator)
for key, value in metadata.items():
print(f" {key}: {value}")
positional = ["Task A", "Task B", "Task C"]
options = {"author": "Alice", "date": "2026-02-14"}
report("Sprint Review", *positional, separator="===", **options)
# == Sprint Review ==
# - Task A
# - Task B
# - Task C
# ===
# author: Alice
# date: 2026-02-14Patrones Prácticos
Patrón 1: Funciones Decoradoras
El uso más común de *args y **kwargs en Python de producción es escribir decoradores. Un decorador envuelve una función con otra. Como no conoces la firma de la función envuelta de antemano, debes usar *args y **kwargs para reenviar todos los argumentos:
import functools
import time
def retry(max_attempts=3, delay=1.0):
"""Retry a function up to max_attempts times on failure."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"Attempt {attempt}/{max_attempts} failed: {e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def fetch_data(url, timeout=10):
"""Simulate fetching data that might fail."""
import random
if random.random() < 0.6:
raise ConnectionError(f"Failed to connect to {url}")
return f"Data from {url}"
# The decorator forwards url and timeout through *args/**kwargs
result = fetch_data("https://api.example.com", timeout=5)
print(result)Patrón 2: Reenvío de init en Subclases
Al crear subclases, a menudo necesitas reenviar argumentos del constructor a la clase padre. *args y **kwargs hacen esto limpio:
class Animal:
def __init__(self, name, species, sound="..."):
self.name = name
self.species = species
self.sound = sound
def speak(self):
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, *args, breed="Unknown", **kwargs):
super().__init__(*args, **kwargs)
self.breed = breed
def info(self):
return f"{self.name} ({self.breed}) - {self.species}"
# All Animal parameters pass through seamlessly
dog = Dog("Rex", "Canine", sound="Woof!", breed="German Shepherd")
print(dog.speak()) # Rex says Woof!
print(dog.info()) # Rex (German Shepherd) - CanineEste patrón es esencial cuando trabajas con jerarquías de clases complejas, especialmente en frameworks como Django, Flask o SQLAlchemy donde extiendes clases base.
Patrón 3: Funciones Wrapper y Proxy
Cuando necesitas interceptar o modificar llamadas a funciones sin cambiar la función original:
def log_call(func):
"""Log every call to a function with its arguments."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result!r}")
return result
return wrapper
@log_call
def calculate_discount(price, discount_pct, tax_rate=0.08):
discounted = price * (1 - discount_pct / 100)
return round(discounted * (1 + tax_rate), 2)
calculate_discount(100, 20, tax_rate=0.1)
# Calling calculate_discount(100, 20, tax_rate=0.1)
# calculate_discount returned 88.0Patrón 4: Constructores de Clientes API
Construir wrappers de API flexibles es un caso de uso clásico:
import json
class APIClient:
def __init__(self, base_url, **default_headers):
self.base_url = base_url.rstrip("/")
self.default_headers = {
"Content-Type": "application/json",
"Accept": "application/json",
**default_headers,
}
def request(self, method, endpoint, *args, **kwargs):
"""Build a request with merged headers and parameters."""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
headers = {**self.default_headers, **kwargs.pop("headers", {})}
request_info = {
"method": method,
"url": url,
"headers": headers,
**kwargs,
}
print(json.dumps(request_info, indent=2, default=str))
return request_info
def get(self, endpoint, **kwargs):
return self.request("GET", endpoint, **kwargs)
def post(self, endpoint, **kwargs):
return self.request("POST", endpoint, **kwargs)
# Usage
client = APIClient(
"https://api.example.com",
Authorization="Bearer token123"
)
client.get("/users", params={"page": 1, "limit": 50})
client.post("/users", data={"name": "Alice"}, headers={"X-Request-ID": "abc123"})Patrón 5: Ciencia de Datos -- Parámetros de Graficado Dinámico
Al construir funciones de análisis de datos, **kwargs te permite pasar configuración a librerías subyacentes:
import pandas as pd
def analyze_column(df, column, **plot_kwargs):
"""Analyze a dataframe column and generate summary statistics."""
stats = {
"count": df[column].count(),
"mean": df[column].mean(),
"std": df[column].std(),
"min": df[column].min(),
"max": df[column].max(),
}
print(f"\nAnalysis of '{column}':")
for stat, value in stats.items():
print(f" {stat}: {value:.2f}")
# Forward any extra kwargs to the plot function
plot_defaults = {"kind": "hist", "bins": 20, "title": f"Distribution of {column}"}
plot_config = {**plot_defaults, **plot_kwargs}
# df[column].plot(**plot_config) # Uncomment with matplotlib installed
print(f" Plot config: {plot_config}")
return stats
# Create sample data
df = pd.DataFrame({
"revenue": [100, 250, 180, 320, 275, 410, 195, 360],
"quantity": [5, 12, 8, 15, 13, 20, 9, 17],
})
# Default analysis
analyze_column(df, "revenue")
# Custom plot settings passed through **kwargs
analyze_column(df, "revenue", kind="box", color="steelblue", figsize=(10, 6))Si trabajas en notebooks de Jupyter y quieres experimentar con firmas de funciones interactivamente, RunCell (opens in a new tab) proporciona un entorno de notebooks potenciado por IA donde puedes probar patrones de *args y **kwargs, obtener sugerencias en tiempo real para el manejo de parámetros, y depurar problemas de paso de argumentos sin salir de tu flujo de trabajo.
Errores Comunes y Cómo Solucionarlos
Aquí están los errores más frecuentes que encuentran los desarrolladores de Python con *args y **kwargs, junto con sus soluciones:
| Error | Mensaje de Error | Causa | Solución |
|---|---|---|---|
| Orden de parámetros incorrecto | SyntaxError: invalid syntax | Colocar **kwargs antes de *args | Usar siempre el orden: regular, *args, solo-palabra-clave, **kwargs |
| Pasar una lista en lugar de desempaquetar | TypeError: func() missing required arguments | Pasar [1,2,3] en lugar de *[1,2,3] | Usar * para desempaquetar: func(*my_list) |
| Argumento de palabra clave duplicado | TypeError: got multiple values for argument 'x' | Misma clave tanto en posicional como en **kwargs | Asegurar que no haya superposición entre argumentos posicionales y claves de diccionario |
| Modificar kwargs directamente | Efectos secundarios inesperados | Mutar el dict kwargs | Usar kwargs.copy() o {**kwargs, ...} |
Olvidar *args en super().init | TypeError: __init__() missing arguments | No reenviar args a la clase padre | Usar super().__init__(*args, **kwargs) |
| Usar valor por defecto mutable con kwargs | Estado compartido entre llamadas | def func(data={}) | Usar None como por defecto: def func(data=None) |
Ejemplo: Argumento de Palabra Clave Duplicado
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
data = {"name": "Alice", "greeting": "Hi"}
# WRONG: name is passed both as positional AND in **data
# greet("Alice", **data)
# TypeError: greet() got multiple values for argument 'name'
# CORRECT: pass only through unpacking
print(greet(**data))
# Hi, Alice!
# OR: remove the duplicate key
print(greet("Alice", **{"greeting": "Hi"}))
# Hi, Alice!Ejemplo: Modificar kwargs de Forma Segura
def process(name, **kwargs):
# WRONG: modifying kwargs directly affects the caller's dict
# kwargs["processed"] = True
# CORRECT: create a new dict
config = {**kwargs, "processed": True}
return {"name": name, **config}
settings = {"timeout": 30, "retries": 3}
result = process("task1", **settings)
print(result)
# {'name': 'task1', 'timeout': 30, 'retries': 3, 'processed': True}
# Original dict is unchanged
print(settings)
# {'timeout': 30, 'retries': 3}*args/**kwargs vs Otros Enfoques
¿Cuándo deberías usar *args y **kwargs en lugar de alternativas? Aquí hay una comparación:
| Enfoque | Sintaxis | Mejor Para | Desventajas |
|---|---|---|---|
*args | def f(*args) | Número desconocido de valores posicionales del mismo tipo | Sin hints de tipo por argumento, sin acceso nombrado |
**kwargs | def f(**kwargs) | Opciones flexibles, paso a través de otras funciones | Sin auto-completado en IDEs, sin verificación estática de tipos |
| Parámetros explícitos | def f(a, b, c) | Conjunto conocido y fijo de argumentos | Rígido; agregar parámetros rompe llamadas existentes |
| Parámetros por defecto | def f(a, b=10) | Parámetros opcionales con valores por defecto razonables | Todavía requiere conocer todas las opciones de antemano |
| Parámetro lista/tupla | def f(items: list) | Colección ordenada de valores | El llamador debe construir la lista explícitamente |
| Parámetro dict | def f(options: dict) | Configuración estructurada | El llamador debe construir el dict explícitamente |
| TypedDict/dataclass | def f(config: Config) | Configuración estructurada y segura en tipos | Más boilerplate, requiere definición de clase |
Guías generales:
- Usa parámetros explícitos cuando el conjunto de argumentos es conocido y estable.
- Usa
*argscuando tu función opera naturalmente sobre un número variable de valores del mismo tipo (comoprint(),max(),min()). - Usa
**kwargscuando necesitas reenviar opciones a otra función, aceptar configuración flexible, o construir APIs extensibles. - Usa ambos cuando escribas decoradores, funciones proxy, o jerarquías de clases que necesiten reenvío completo de argumentos.
- Usa TypedDict o dataclass cuando quieras auto-completado en IDE y verificación estática de tipos para configuración estructurada.
Agregando Hints de Tipo con *args y **kwargs
Python 3.11+ te permite agregar hints de tipo usando *args y **kwargs con Unpack y TypedDict:
# Basic type hints (all args same type)
def add_numbers(*args: float) -> float:
return sum(args)
def set_options(**kwargs: str) -> dict[str, str]:
return kwargs
# Python 3.11+: precise kwargs typing with TypedDict
from typing import TypedDict, Unpack
class ConnectionOptions(TypedDict, total=False):
timeout: int
retries: int
ssl: bool
def connect(host: str, port: int, **kwargs: Unpack[ConnectionOptions]) -> dict:
return {"host": host, "port": port, **kwargs}
# IDE now knows the valid keyword arguments
result = connect("localhost", 5432, timeout=30, ssl=True)
print(result)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'ssl': True}Preguntas Frecuentes
¿Qué significan *args y **kwargs?
Los nombres args y kwargs son abreviaturas de "arguments" y "keyword arguments" respectivamente. Son nombres puramente convencionales -- la magia real viene de los operadores prefijo * y **. El asterisco simple * le dice a Python que empaquete argumentos posicionales extra en una tupla, mientras que el doble asterisco ** empaqueta argumentos de palabra clave extra en un diccionario. Verás estos nombres usados en prácticamente cada base de código de Python, tutorial y librería porque son la convención universalmente reconocida, pero el lenguaje no requiere estos nombres específicos.
¿Puedo usar nombres distintos a args y kwargs?
Sí. Los nombres args y kwargs son convenciones, no requisitos de sintaxis. El comportamiento de desempaquetado viene enteramente de los prefijos * y **. Podrías escribir *values, *items, *numbers, **options, **config, **params, o cualquier otro identificador válido de Python. Sin embargo, mantener *args y **kwargs es fuertemente recomendado en la mayoría de los casos porque cada desarrollador de Python los reconoce inmediatamente. Usa nombres personalizados solo cuando un nombre más descriptivo genuinamente mejore la legibilidad, como *paths en una función de manejo de archivos o **headers en un cliente HTTP.
¿Cuál es el orden correcto de parámetros en una función de Python?
Python impone un orden estricto: primero vienen los parámetros posicionales regulares, luego *args, luego parámetros solo-palabra-clave (con valores por defecto), y finalmente **kwargs. El orden completo es: def func(pos1, pos2, default1=val, *args, kw_only1, kw_only2=val, **kwargs). Violar este orden produce un SyntaxError. Un mnemotécnico útil es "posicional, star-args, solo-palabra-clave, double-star-kwargs" -- el número de asteriscos aumenta de izquierda a derecha.
¿Cuándo debería usar *args vs un parámetro lista?
Usa *args cuando cada argumento es un valor separado e independiente y el llamador debería pasarlos naturalmente sin construir un contenedor: print("a", "b", "c") es más natural que print(["a", "b", "c"]). Usa un parámetro lista cuando los valores formen lógicamente una colección que el llamador ya tiene en una variable, o cuando necesitas distinguir entre la colección y otros parámetros. Las funciones built-in como max(), min(), y print() usan *args porque la convención de llamada se siente natural, mientras que funciones como sorted(iterable) toman un único iterable porque la entrada es inherentemente una secuencia.
¿Son *args y **kwargs lentos?
La sobrecarga de *args y **kwargs es mínima. Python crea una tupla para *args y un diccionario para **kwargs en cada llamada, lo cual involucra pequeñas asignaciones de memoria. En benchmarks, la diferencia comparada con parámetros explícitos es típicamente unos pocos cientos de nanosegundos por llamada -- irrelevante para virtualmente todo el código del mundo real. Necesitarías millones de llamadas en un bucle ajustado antes de que esta sobrecarga se vuelva medible. Enfócate en la eficiencia algorítmica y la optimización de I/O en lugar de evitar *args/**kwargs. La flexibilidad y mantenibilidad del código que proporcionan superan con creces cualquier costo de micro-rendimiento.
Conclusión
Los *args y **kwargs de Python son dos de las características más prácticas del lenguaje. Resuelven un problema fundamental: cómo escribir funciones que sean lo suficientemente flexibles para aceptar números variables de argumentos sin sacrificar legibilidad.
Los puntos clave:
*argsrecolecta argumentos posicionales extra en una tupla. Úsalo cuando tu función deba aceptar cualquier número de valores.**kwargsrecolecta argumentos de palabra clave extra en un diccionario. Úsalo para opciones flexibles, paso de configuración, y APIs extensibles.- El orden de parámetros es siempre: regular,
*args, solo-palabra-clave,**kwargs. - El desempaquetado con
*y**funciona en llamadas a funciones, construcción de listas y fusión de diccionarios. - Los decoradores son el caso de uso del mundo real más importante -- dependen de
*argsy**kwargspara envolver cualquier función independientemente de su firma.
Comienza con parámetros explícitos para funciones con firmas conocidas y estables. Recurre a *args y **kwargs cuando necesites flexibilidad: decoradores, reenvío de subclases, funciones wrapper y constructores de API. Una vez que internalices las mecánicas de empaquetado y desempaquetado, te encontrarás escribiendo código Python más limpio y reutilizable.