Skip to content

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.

  • *args recolecta argumentos posicionales extra en una tupla.
  • **kwargs recolecta 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())                 # 0

Dentro 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 seconds

Ejemplo 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.6

Ejemplo 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.csv

Entendiendo **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: Seattle

Dentro 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:

  1. Parámetros posicionales regulares
  2. *args (posicional variable)
  3. Parámetros solo-palabra-clave (después de *args)
  4. **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ónTipoSintaxisEjemploDescripción
1roPosicionalparama, bRequerido, llenado por posición
2doPor defectoparam=valuec=10Opcional, llenado por posición o nombre
3roPosicional variable*args*argsRecolecta argumentos posicionales extra
4toSolo-palabra-claveparam (después de *)option=TrueDebe pasarse por nombre
5toPalabra clave variable**kwargs**kwargsRecolecta 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)
# 60

Desempaquetado 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))  # 30

Tambié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-14

Patrones 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) - Canine

Este 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.0

Patró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:

ErrorMensaje de ErrorCausaSolución
Orden de parámetros incorrectoSyntaxError: invalid syntaxColocar **kwargs antes de *argsUsar siempre el orden: regular, *args, solo-palabra-clave, **kwargs
Pasar una lista en lugar de desempaquetarTypeError: func() missing required argumentsPasar [1,2,3] en lugar de *[1,2,3]Usar * para desempaquetar: func(*my_list)
Argumento de palabra clave duplicadoTypeError: got multiple values for argument 'x'Misma clave tanto en posicional como en **kwargsAsegurar que no haya superposición entre argumentos posicionales y claves de diccionario
Modificar kwargs directamenteEfectos secundarios inesperadosMutar el dict kwargsUsar kwargs.copy() o {**kwargs, ...}
Olvidar *args en super().initTypeError: __init__() missing argumentsNo reenviar args a la clase padreUsar super().__init__(*args, **kwargs)
Usar valor por defecto mutable con kwargsEstado compartido entre llamadasdef 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:

EnfoqueSintaxisMejor ParaDesventajas
*argsdef f(*args)Número desconocido de valores posicionales del mismo tipoSin hints de tipo por argumento, sin acceso nombrado
**kwargsdef f(**kwargs)Opciones flexibles, paso a través de otras funcionesSin auto-completado en IDEs, sin verificación estática de tipos
Parámetros explícitosdef f(a, b, c)Conjunto conocido y fijo de argumentosRígido; agregar parámetros rompe llamadas existentes
Parámetros por defectodef f(a, b=10)Parámetros opcionales con valores por defecto razonablesTodavía requiere conocer todas las opciones de antemano
Parámetro lista/tupladef f(items: list)Colección ordenada de valoresEl llamador debe construir la lista explícitamente
Parámetro dictdef f(options: dict)Configuración estructuradaEl llamador debe construir el dict explícitamente
TypedDict/dataclassdef f(config: Config)Configuración estructurada y segura en tiposMá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 *args cuando tu función opera naturalmente sobre un número variable de valores del mismo tipo (como print(), max(), min()).
  • Usa **kwargs cuando 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:

  • *args recolecta argumentos posicionales extra en una tupla. Úsalo cuando tu función deba aceptar cualquier número de valores.
  • **kwargs recolecta 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 *args y **kwargs para 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.

📚