Python & AI Tutorials Logo
Programación Python

20. Parámetros y argumentos de funciones

En el Capítulo 19, aprendimos a definir y llamar funciones (functions) con parámetros básicos. Ahora exploraremos en profundidad el sistema flexible de parámetros y argumentos de Python. Comprender estos mecanismos te permite escribir funciones que sean a la vez potentes y fáciles de usar.

20.1) Argumentos posicionales y de palabra clave

Cuando llamas a una función, puedes pasar argumentos de dos formas fundamentales: por posición o por nombre (palabra clave).

20.1.1) Argumentos posicionales

Los argumentos posicionales se emparejan con los parámetros según su orden. El primer argumento va al primer parámetro, el segundo al segundo parámetro, y así sucesivamente.

python
def calculate_discount(price, discount_percent):
    """Calculate the final price after applying a discount."""
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
# Pasar argumentos por posición
result = calculate_discount(100, 20)
print(result)

Output:

80.0

En este ejemplo, 100 se asigna a price y 20 a discount_percent basándose únicamente en sus posiciones en la llamada a la función.

El orden importa de forma crítica con los argumentos posicionales:

python
# Ejemplo: queremos calcular un artículo de $100 con un 20% de descuento
 
# Orden correcto: primero price, luego discount
print(calculate_discount(100, 20))
 
# Orden incorrecto: primero discount, luego price
print(calculate_discount(20, 100))

Output:

80.0
-16.0

Cuando intercambias los argumentos, Python no sabe que cometiste un error: simplemente los asigna en orden. Esto produce un resultado matemáticamente válido, pero lógicamente incorrecto (¡un precio negativo!).

20.1.2) Argumentos de palabra clave

Los argumentos de palabra clave especifican explícitamente qué parámetro recibe qué valor usando el nombre del parámetro seguido de un signo igual y el valor. Esto hace que tu código sea más legible y te protege de errores de orden.

python
def create_user_profile(username, email, age):
    """Create a user profile with the given information."""
    profile = f"User: {username}\nEmail: {email}\nAge: {age}"
    return profile
 
# Usar argumentos de palabra clave
profile = create_user_profile(username="alice_smith", email="alice@example.com", age=28)
print(profile)

Output:

User: alice_smith
Email: alice@example.com
Age: 28

Con argumentos de palabra clave, el orden no importa:

python
# Mismo resultado, distinto orden
profile1 = create_user_profile(username="bob", email="bob@example.com", age=35)
profile2 = create_user_profile(age=35, username="bob", email="bob@example.com")
profile3 = create_user_profile(email="bob@example.com", age=35, username="bob")
 
# Los tres producen resultados idénticos
print(profile1 == profile2 == profile3)

Output:

True

Esta flexibilidad es especialmente valiosa cuando una función tiene muchos parámetros, lo que facilita ver qué valor corresponde a qué parámetro.

20.1.3) Mezclar argumentos posicionales y de palabra clave

Puedes combinar ambos estilos en una sola llamada a la función, pero hay una regla importante: los argumentos posicionales deben ir antes que los argumentos de palabra clave.

python
def format_address(street, city, state, zip_code):
    """Format a mailing address."""
    return f"{street}\n{city}, {state} {zip_code}"
 
# Válido: primero argumentos posicionales, luego argumentos de palabra clave
address = format_address("123 Main St", "Springfield", state="IL", zip_code="62701")
print(address)

Output:

123 Main St
Springfield, IL 62701

Aquí, "123 Main St" y "Springfield" son posicionales (se asignan a street y city), mientras que state y zip_code se especifican por nombre.

Intentar colocar argumentos posicionales después de argumentos de palabra clave provoca un error:

python
# Inválido: argumento posicional después de argumento de palabra clave
# address = format_address(street="123 Main St", "Springfield", state="IL", zip_code="62701")
# SyntaxError: positional argument follows keyword argument

Python impone esta regla porque, una vez que empiezas a usar argumentos de palabra clave, se vuelve ambiguo qué parámetro posicional debería rellenar un argumento posterior sin nombre.

20.1.4) Cuándo usar cada estilo

Usa argumentos posicionales cuando:

  • La función tiene pocos parámetros (normalmente 1-3)
  • El orden de los parámetros es obvio e intuitivo
  • La función se usa comúnmente y el orden es bien conocido
python
# Obvio y conciso
print(len("hello"))
result = max(10, 20, 5)

Usa argumentos de palabra clave cuando:

  • La función tiene muchos parámetros
  • Los significados de los parámetros no son inmediatamente obvios
  • Quieres omitir algunos parámetros que tienen valores por defecto (lo veremos a continuación)
  • Quieres que tu código se autodocumente
python
# Claro y explícito
user = create_user_profile(username="charlie", email="charlie@example.com", age=42)

20.2) Valores por defecto de parámetros

Las funciones pueden especificar valores por defecto para los parámetros. Cuando quien llama no proporciona un argumento para un parámetro con valor por defecto, Python usa el valor por defecto en su lugar.

20.2.1) Definir parámetros con valores por defecto

Los valores por defecto se especifican en la definición de la función usando el operador de asignación:

python
def greet_user(name, greeting="Hello"):
    """Greet a user with a customizable greeting."""
    return f"{greeting}, {name}!"
 
# Usar el saludo por defecto
print(greet_user("Alice"))
 
# Proporcionar un saludo personalizado
print(greet_user("Bob", "Good morning"))
print(greet_user("Carol", greeting="Hi"))

Output:

Hello, Alice!
Good morning, Bob!
Hi, Carol!

El parámetro greeting tiene un valor por defecto "Hello". Cuando llamas a greet_user("Alice"), Python usa este valor por defecto. Cuando proporcionas un segundo argumento, reemplaza el valor por defecto.

20.2.2) Los parámetros con valores por defecto deben ir después de los parámetros obligatorios

Python requiere que los parámetros con valores por defecto aparezcan después de todos los parámetros sin valores por defecto. Esta regla evita la ambigüedad sobre qué argumentos corresponden a qué parámetros.

python
# Correcto: primero parámetros obligatorios, luego valores por defecto
def create_product(name, price, category="General", in_stock=True):
    """Create a product record."""
    return {
        "name": name,
        "price": price,
        "category": category,
        "in_stock": in_stock
    }
 
product = create_product("Laptop", 999.99)
print(product)

Output:

{'name': 'Laptop', 'price': 999.99, 'category': 'General', 'in_stock': True}

Intentar colocar un parámetro obligatorio después de uno con valor por defecto provoca un error de sintaxis:

python
# Invalid: required parameter after default parameter
# def invalid_function(name="Unknown", age):
#     return f"{name} is {age} years old"
# SyntaxError: non-default argument follows default argument

Esto tiene sentido: si name tiene un valor por defecto pero age no, ¿cómo sabría Python si invalid_function(25) significa name=25 con age faltando, o age=25 con name usando su valor por defecto? La regla elimina esta ambigüedad.

20.2.3) Usos prácticos de los parámetros por defecto

Los parámetros por defecto son muy útiles en funciones donde ciertos argumentos rara vez cambian:

python
def calculate_shipping(weight, distance, express=False):
    """Calculate shipping cost based on weight and distance."""
    base_rate = 0.50 * weight + 0.10 * distance
    
    if express:
        base_rate *= 2  # Los envíos express cuestan el doble
    
    return round(base_rate, 2)
 
# La mayoría de los envíos son estándar
standard_cost = calculate_shipping(5, 100)
print(f"Standard: ${standard_cost}")
 
# Ocasionalmente alguien necesita express
express_cost = calculate_shipping(5, 100, express=True)
print(f"Express: ${express_cost}")

Output:

Standard: $12.5
Express: $25.0

Este diseño hace que el caso común (envío estándar) sea sencillo de usar, y aun así soporta el caso menos común (envío express) cuando se necesita.

20.2.4) Múltiples valores por defecto y sobreescritura selectiva

Cuando una función tiene múltiples parámetros con valores por defecto, puedes sobrescribir cualquier combinación de ellos usando argumentos de palabra clave:

python
def format_currency(amount, currency="USD", show_symbol=True, decimal_places=2):
    """Format a number as currency."""
    symbols = {"USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥"}
    
    formatted = f"{amount:.{decimal_places}f}"
    
    if show_symbol and currency in symbols:
        formatted = f"{symbols[currency]}{formatted}"
    
    return formatted
 
# Usar todos los valores por defecto
print(format_currency(42.5))
 
# Sobrescribir solo currency
print(format_currency(42.5, currency="EUR"))
 
# Sobrescribir múltiples valores por defecto
print(format_currency(42.5, currency="JPY", decimal_places=0))

Output:

$42.50
€42.50
¥42

Esta flexibilidad permite que quien llama personalice exactamente lo que necesita, manteniendo la llamada a la función concisa.

20.3) Listas de argumentos de longitud variable con *args

A veces quieres que una función acepte cualquier número de argumentos sin saber de antemano cuántos habrá. Python proporciona *args para este propósito.

20.3.1) Comprender *args

La sintaxis *args en una lista de parámetros recopila todos los argumentos posicionales extra en una tupla. El nombre args es una convención (abreviatura de "arguments"), pero puedes usar cualquier nombre de parámetro válido después del asterisco.

python
def calculate_total(*numbers):
    """Calculate the sum of any number of values."""
    total = 0
    for num in numbers:
        total += num
    return total
 
# Funciona con cualquier número de argumentos
print(calculate_total(10))
print(calculate_total(10, 20))
print(calculate_total(10, 20, 30, 40))
print(calculate_total())

Output:

10
30
100
0

Dentro de la función, numbers es una tupla que contiene todos los argumentos posicionales pasados a la función. Cuando no se proporcionan argumentos, es una tupla vacía.

20.3.2) Combinar parámetros normales con *args

Puedes tener parámetros normales antes de *args. Los parámetros normales consumen los primeros argumentos, y *args recopila el resto:

python
def create_team(team_name, *members):
    """Create a team with a name and any number of members."""
    member_list = ", ".join(members)
    return f"Team {team_name}: {member_list}"
 
# El primer argumento va a team_name, el resto va a members
print(create_team("Alpha", "Alice", "Bob"))
print(create_team("Beta", "Carol"))
print(create_team("Gamma", "Dave", "Eve", "Frank", "Grace"))

Output:

Team Alpha: Alice, Bob
Team Beta: Carol
Team Gamma: Dave, Eve, Frank, Grace

El primer argumento ("Alpha", "Beta" o "Gamma") se asigna a team_name, y todos los argumentos restantes se recopilan en la tupla members.

20.4) Parámetros de solo palabra clave y parámetros **kwargs

Python proporciona dos mecanismos adicionales para manejar argumentos: los parámetros de solo palabra clave y **kwargs para recopilar argumentos arbitrarios de palabra clave.

20.4.1) Parámetros de solo palabra clave

Los parámetros de solo palabra clave deben especificarse usando argumentos de palabra clave: no se pueden pasar de forma posicional. Los creas colocándolos después de un * o después de *args en la lista de parámetros.

python
def create_account(username, *, email, age):
    """Create an account. Email and age must be specified by name."""
    return {
        "username": username,
        "email": email,
        "age": age
    }
 
# Correcto: email y age especificados por palabra clave
account = create_account("alice", email="alice@example.com", age=28)
print(account)
 
# Invalid: trying to pass email and age positionally
# account = create_account("bob", "bob@example.com", 30)
# TypeError: create_account() takes 1 positional argument but 3 were given

Output:

{'username': 'alice', 'email': 'alice@example.com', 'age': 28}

El * en la lista de parámetros actúa como separador. Todo lo que esté después debe pasarse como argumento de palabra clave. Esto es útil cuando quieres obligar a quien llama a ser explícito sobre ciertos parámetros, haciendo el código más legible y menos propenso a errores.

También puedes combinar parámetros normales, *args y parámetros de solo palabra clave:

python
def log_event(event_type, *details, severity="INFO", timestamp=None):
    """Log an event with optional details and metadata."""
    # Aprenderemos sobre el módulo datetime en detalle en el Capítulo 39,
    # pero por ahora, solo debes saber que estas líneas obtienen la hora actual
    # y la formatean como una cadena de marca de tiempo
    from datetime import datetime
    
    if timestamp is None:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    details_str = " | ".join(details)
    return f"[{timestamp}] {severity}: {event_type} - {details_str}"
 
# event_type es posicional, details se recopila con *details,
# severity y timestamp son de solo palabra clave
print(log_event("Login", "User: alice", "IP: 192.168.1.1"))
print(log_event("Error", "Database connection failed", severity="ERROR"))

Output (timestamp will vary based on when you run the code):

[2025-12-18 19:29:16] INFO: Login - User: alice | IP: 192.168.1.1
[2025-12-18 19:29:16] ERROR: Error - Database connection failed

20.4.2) Comprender **kwargs

La sintaxis **kwargs recopila todos los argumentos extra de palabra clave en un diccionario. Al igual que args, el nombre kwargs es convencional (abreviatura de "keyword arguments"), pero puedes usar cualquier nombre válido después del doble asterisco.

python
def create_product(**attributes):
    """Create a product with any number of attributes."""
    product = {}
    for key, value in attributes.items():
        product[key] = value
    return product
 
# Pasa cualquier argumento de palabra clave que quieras
laptop = create_product(name="Laptop", price=999.99, brand="TechCorp", in_stock=True)
print(laptop)
 
phone = create_product(name="Phone", price=699.99, color="Black")
print(phone)

Output:

{'name': 'Laptop', 'price': 999.99, 'brand': 'TechCorp', 'in_stock': True}
{'name': 'Phone', 'price': 699.99, 'color': 'Black'}

Dentro de la función, attributes es un diccionario donde las claves son los nombres de los parámetros y los valores son los argumentos pasados.

20.4.3) Combinar parámetros normales, *args y **kwargs

Puedes usar todos estos mecanismos juntos, pero deben aparecer en un orden específico:

  1. Parámetros posicionales normales
  2. *args (si está presente)
  3. Parámetros de solo palabra clave (si están presentes)
  4. **kwargs (si está presente)
python
def complex_function(required, *args, keyword_only, **kwargs):
    """Demonstrate all parameter types together."""
    print(f"Required: {required}")
    print(f"Args: {args}")
    print(f"Keyword-only: {keyword_only}")
    print(f"Kwargs: {kwargs}")
 
complex_function(
    "value1",           # required
    "value2", "value3", # args
    keyword_only="kw",  # keyword_only
    extra1="e1",        # kwargs
    extra2="e2"         # kwargs
)

Output:

Required: value1
Args: ('value2', 'value3')
Keyword-only: kw
Kwargs: {'extra1': 'e1', 'extra2': 'e2'}

Esta flexibilidad es muy potente, pero conviene usarla con criterio. La mayoría de las funciones no necesitan todos estos mecanismos.

20.4.4) Caso de uso práctico: funciones de configuración

Un uso común de **kwargs es crear funciones que acepten opciones de configuración:

python
def connect_to_database(host, port, **options):
    """Connect to a database with flexible configuration options."""
    connection_string = f"Connecting to {host}:{port}"
    
    # Procesar cualquier opción adicional
    if options.get("ssl"):
        connection_string += " with SSL"
    
    if options.get("timeout"):
        connection_string += f" (timeout: {options['timeout']}s)"
    
    if options.get("pool_size"):
        connection_string += f" (pool size: {options['pool_size']})"
    
    return connection_string
 
# Conexión básica
print(connect_to_database("localhost", 5432))
 
# Con SSL
print(connect_to_database("db.example.com", 5432, ssl=True))
 
# Con múltiples opciones
print(connect_to_database("db.example.com", 5432, ssl=True, timeout=30, pool_size=10))

Output:

Connecting to localhost:5432
Connecting to db.example.com:5432 with SSL
Connecting to db.example.com:5432 with SSL (timeout: 30s) (pool size: 10)

Este patrón permite que la función acepte cualquier número de parámetros opcionales de configuración sin definirlos todos explícitamente en la lista de parámetros.

Posicional

Posicional extra

Solo palabra clave

Palabra clave extra

Llamada a función

¿Tipo de parámetro?

Parámetros normales

Tupla *args

Parámetros de solo palabra clave

Diccionario **kwargs

Asignados por posición

Recopilados en una tupla

Debes usar el nombre

Recopilados en un diccionario

20.5) Desempaquetado de argumentos al llamar funciones

Igual que *args y **kwargs recopilan argumentos al definir funciones, puedes usar * y ** para desempaquetar (unpack) colecciones al llamar funciones.

20.5.1) Desempaquetar secuencias con *

El operador * desempaqueta una secuencia (lista, tupla, etc.) en argumentos posicionales separados:

python
def calculate_rectangle_area(width, height):
    """Calculate the area of a rectangle."""
    return width * height
 
# En lugar de pasar los argumentos individualmente
dimensions = [5, 10]
area = calculate_rectangle_area(dimensions[0], dimensions[1])
print(area)
 
# Desempaquetar la lista directamente
area = calculate_rectangle_area(*dimensions)
print(area)

Output:

50
50

Cuando escribes *dimensions, Python desempaqueta la lista [5, 10] en dos argumentos separados, como si hubieras escrito calculate_rectangle_area(5, 10).

Esto funciona con cualquier iterable:

python
def format_name(first, middle, last):
    """Format a full name."""
    return f"{first} {middle} {last}"
 
# Desempaquetar una tupla
name_tuple = ("John", "Q", "Public")
print(format_name(*name_tuple))
 
# Desempaquetar una lista
name_list = ["Jane", "M", "Doe"]
print(format_name(*name_list))
 
# Incluso desempaquetar una cadena (cada carácter se convierte en un argumento)
# Esto solo funciona si la función espera el número correcto de argumentos
def show_first_three(a, b, c):
    return f"{a}, {b}, {c}"
 
print(show_first_three(*"ABC"))

Output:

John Q Public
Jane M Doe
A, B, C

20.5.2) Desempaquetar diccionarios con **

El operador ** desempaqueta un diccionario en argumentos de palabra clave:

python
def create_user(username, email, age):
    """Create a user profile."""
    return f"User: {username}, Email: {email}, Age: {age}"
 
# Diccionario con claves que coinciden con los nombres de los parámetros
user_data = {
    "username": "alice",
    "email": "alice@example.com",
    "age": 28
}
 
# Desempaquetar el diccionario
profile = create_user(**user_data)
print(profile)

Output:

User: alice, Email: alice@example.com, Age: 28

Cuando escribes **user_data, Python desempaqueta el diccionario en argumentos de palabra clave, equivalente a:

python
create_user(username="alice", email="alice@example.com", age=28)

Las claves del diccionario deben coincidir con los nombres de los parámetros de la función, o obtendrás un error:

python
# Invalid: dictionary key doesn't match parameter name
invalid_data = {"name": "bob", "email": "bob@example.com", "age": 30}
# profile = create_user(**invalid_data)
# TypeError: create_user() got an unexpected keyword argument 'name'

20.5.3) Combinar desempaquetado con argumentos normales

Puedes mezclar argumentos desempaquetados con argumentos normales:

python
def calculate_total(base_price, tax_rate, discount):
    """Calculate total price after tax and discount."""
    subtotal = base_price * (1 + tax_rate)
    total = subtotal * (1 - discount)
    return round(total, 2)
 
# Algunos argumentos normales, otros desempaquetados
pricing = [0.08, 0.10]  # tax_rate and discount
total = calculate_total(100, *pricing)
print(total)

Output:

97.2

También puedes desempaquetar múltiples colecciones en una sola llamada:

python
def create_full_address(street, city, state, zip_code, country):
    """Create a complete address."""
    return f"{street}, {city}, {state} {zip_code}, {country}"
 
street_address = ["123 Main St", "Springfield"]
location_details = ["IL", "62701", "USA"]
 
address = create_full_address(*street_address, *location_details)
print(address)

Output:

123 Main St, Springfield, IL 62701, USA

20.5.4) Ejemplo práctico: llamadas a funciones flexibles

El desempaquetado es particularmente útil cuando trabajas con datos de fuentes externas:

python
def send_email(recipient, subject, body, cc=None, bcc=None):
    """Send an email with optional CC and BCC."""
    message = f"To: {recipient}\nSubject: {subject}\n\n{body}"
    
    if cc:
        message += f"\nCC: {cc}"
    if bcc:
        message += f"\nBCC: {bcc}"
    
    return message
 
# Datos de email desde un archivo de configuración o una base de datos
email_config = {
    "recipient": "user@example.com",
    "subject": "Welcome",
    "body": "Thank you for signing up!",
    "cc": "manager@example.com"
}
 
# Desempaquetar la configuración directamente
result = send_email(**email_config)
print(result)

Output:

To: user@example.com
Subject: Welcome
 
Thank you for signing up!
CC: manager@example.com

Este patrón facilita pasar argumentos de función como estructuras de datos, lo cual es común al construir APIs o procesar archivos de configuración.

Operador *

Operador **

Colección

Tipo de desempaquetado

Desempaquetado de secuencias

Desempaquetado de diccionarios

Lista/Tupla → Args posicionales

Diccionario → Args de palabra clave

Llamada a función

20.6) La trampa de los argumentos por defecto mutables (por qué persisten los valores por defecto de listas)

Uno de los escollos más notorios de Python implica usar objetos mutables (como listas o diccionarios) como valores por defecto de parámetros. Comprender este problema es crucial para escribir funciones correctas.

20.6.1) El problema: valores por defecto mutables compartidos

Considera esta función aparentemente inocente:

python
def add_student(name, grades=[]):
    """Add a student with their grades."""
    grades.append(name)
    return grades
 
# Primera llamada
students1 = add_student("Alice")
print(students1)
 
# Segunda llamada: esperando una lista nueva
students2 = add_student("Bob")
print(students2)
 
# Tercera llamada
students3 = add_student("Carol")
print(students3)

Output:

['Alice']
['Alice', 'Bob']
['Alice', 'Bob', 'Carol']

Este comportamiento sorprende a muchos programadores. Cada llamada a add_student() sin proporcionar un argumento grades usa el mismo objeto lista, no uno nuevo. La lista persiste a través de las llamadas a la función, acumulando valores.

20.6.2) Por qué ocurre esto: los valores por defecto se crean una sola vez

La clave para comprender este comportamiento es saber cuándo se crean los valores por defecto. Python evalúa los valores por defecto de parámetros una vez, cuando se define la función, no cada vez que se llama la función.

python
def demonstrate_default_creation():
    """Show when defaults are created."""
    print("Function defined!")
 
def use_default(value=demonstrate_default_creation()):
    """Use a default that calls a function."""
    return value
 
# El mensaje se imprime cuando la función se DEFINE, no cuando se llama

Output:

Function defined!

Cuando Python encuentra la línea def use_default, evalúa el parámetro por defecto value=demonstrate_default_creation(). Esto llama a demonstrate_default_creation(), que imprime "Function defined!" de inmediato. Las llamadas posteriores a use_default() no vuelven a evaluar el valor por defecto, así que no se imprime nada adicional.

Cuando Python encuentra def add_student(name, grades=[]):, crea un objeto lista vacío y lo guarda como el valor por defecto para grades. Cada llamada posterior que no proporcione un argumento grades usa ese mismo objeto lista.

Aquí tienes una demostración más clara usando identidad de objeto:

python
def show_list_identity(items=[]):
    """Show that the same list object is reused."""
    print(f"List ID: {id(items)}")
    items.append("item")
    return items
 
# Cada llamada usa el mismo objeto lista (mismo ID)
show_list_identity()
show_list_identity()
show_list_identity()

Output:

List ID: 140234567890123
List ID: 140234567890123
List ID: 140234567890123

Los números de ID exactos variarán en tu sistema, pero observa que las tres llamadas muestran el mismo valor de ID, lo que prueba que están usando el mismo objeto lista. La función id() devuelve un identificador único para cada objeto en memoria: cuando los IDs coinciden, es el mismo objeto.

20.6.3) El patrón correcto: usar None como valor por defecto

La solución estándar es usar None como valor por defecto y crear un objeto mutable nuevo dentro de la función:

python
def add_student_correct(name, grades=None):
    """Add a student with their grades (correct version)."""
    if grades is None:
        grades = []  # Crear una lista NUEVA cada vez
    
    grades.append(name)
    return grades
 
# Ahora cada llamada obtiene su propia lista
students1 = add_student_correct("Alice")
print(students1)
 
students2 = add_student_correct("Bob")
print(students2)
 
students3 = add_student_correct("Carol")
print(students3)

Output:

['Alice']
['Bob']
['Carol']

Este patrón funciona porque None es inmutable y se crea una lista nueva dentro del cuerpo de la función cada vez que grades es None.

20.6.4) El mismo problema con diccionarios

Este problema afecta a todos los tipos mutables, no solo a las listas:

python
# WRONG: Dictionary default
def create_config_wrong(key, value, config={}):
    """Create a configuration (BUGGY VERSION)."""
    config[key] = value
    return config
 
config1 = create_config_wrong("theme", "dark")
print(config1)
 
config2 = create_config_wrong("language", "en")
print(config2)
 
print("---")
 
# CORRECT: None as default
def create_config_correct(key, value, config=None):
    """Create a configuration (CORRECT VERSION)."""
    if config is None:
        config = {}
    
    config[key] = value
    return config
 
config1 = create_config_correct("theme", "dark")
print(config1)
 
config2 = create_config_correct("language", "en")
print(config2)

Output:

{'theme': 'dark'}
{'theme': 'dark', 'language': 'en'}
---
{'theme': 'dark'}
{'language': 'en'}

20.6.5) Resumen: la regla de oro

Nunca uses objetos mutables (listas, diccionarios, conjuntos) como valores por defecto de parámetros. Usa siempre None y crea el objeto mutable dentro de la función:

python
# ❌ WRONG
def function(items=[]):
    pass
 
# ✅ CORRECT
def function(items=None):
    if items is None:
        items = []
    # Ahora usa items de forma segura

Este patrón garantiza que cada llamada a la función tenga su propio objeto mutable independiente, evitando bugs misteriosos donde los datos se “filtran” entre llamadas.


En este capítulo, hemos explorado en profundidad el sistema flexible de parámetros y argumentos de Python. Has aprendido cómo usar argumentos posicionales y de palabra clave, proporcionar valores por defecto, manejar números variables de argumentos con *args y **kwargs, desempaquetar colecciones al llamar funciones y evitar la trampa de los argumentos por defecto mutables.

Estos mecanismos te dan herramientas potentes para diseñar interfaces de funciones que sean tanto flexibles como fáciles de usar. A medida que escribas más funciones, desarrollarás intuición sobre qué patrones de parámetros se adaptan mejor a diferentes situaciones. La clave es equilibrar flexibilidad con claridad: haz que tus funciones sean fáciles de llamar correctamente y difíciles de llamar incorrectamente.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai