¿Cuál es el problema?
Imagina que estás escribiendo una función para añadir tareas a una lista de tareas pendientes:
def add_task(task, todo_list=[]):
todo_list.append(task)
return todo_list
# Añadimos la primera tarea print(add_task("Comprar leche")) # ['Comprar leche']os la segunda tarea print(add_task("Pasear al perro")) # Esperamos: ['Pasear al perro'] # Obtenemos: ['Comprar leche', 'Pasear al perro']⚠️ ¿Qué ha pasado? ¡Esperábamos recibir una nueva lista con una tarea, pero la segunda tarea se añadió a la primera! Además, no guardamos la lista anterior en ningún lugar. ¿Magia? No, son argumentos predeterminados mutables.
¿Por qué sucede esto?
Cuando Python crea una función, crea un objeto por defecto una vez en el momento de la definición de la función, y no en cada llamada. Este objeto se guarda y se reutiliza en cada llamada posterior.
Veamos qué está pasando realmente:
def add_task(task, todo_list=[]):
todo_list.append(task)
print(f"List ID: {id(todo_list)}")
return todo_list
add_task("First task") # ID de la lista: 140234567891234add_task("Segunda tarea") # ID de la lista: 140234567891234 (¡el mismo!)add_task("Tercera tarea") # ID de la lista: 140234567891234 (¡y otra vez!)💡 Punto clave: Cada vez se utiliza el mismo objeto de la lista. Todas las llamadas a la función funcionan con la misma lista en la memoria, por lo que todos los cambios se acumulan.
¿Qué tipos de datos se ven afectados?
Este problema afecta a todos los tipos de datos mutables en Python. Esto incluye listas, diccionarios, conjuntos y objetos personalizados. Pero los números, cadenas y tuplas (tipos inmutables) funcionan normalmente, porque no se pueden cambiar, solo se pueden crear nuevos.
# Código problemático con el diccionario def add_user(name, users_dict={}):
users_dict[name] = True
return users_dict
print(add_user("Alexey")) # {'Alexey': True}print(add_user("Maria")) # {'Alexey': True, 'Maria': True} - ¡ambos están aquí!# Código seguro con la línea (immutable)def greet(name, greeting="Hola"): greeting = greeting + ", " + name
return greeting
print(greet("Ivan")) # Hola, Ivanprint(greet("Olga")) # Hola, Olga - todo funciona como se esperabaLa decisión correcta.
La solución clásica a este problema es usar None como valor predeterminado y luego crear un nuevo objeto dentro de la función:
def add_task(task, todo_list=None):
if todo_list is None:
todo_list = []
todo_list.append(task)
return todo_list
# Ahora todo funciona correctamenteprint(add_task("Comprar leche")) # ['Comprar leche']print(add_task("Pasear al perro")) # ['Pasear al perro']✅ Regla: Cada vez que no pasamos la lista, se crea una nueva lista vacía. ¡Problema resuelto!
Lo mismo funciona para los diccionarios:
def create_profile(name, data=None):
if data is None:
data = {}
data['name'] = name
data['created_at'] = 'today'
return data
profile1 = create_profile("Alexey")
profile2 = create_profile("Maria")
print(profile1) # {'name': 'Alexey', 'created_at': 'hoy'}print(profile2) # {'name': 'Maria', 'created_at': 'hoy'}
¿Cuándo puede ser útil?
Curiosamente, a veces este comportamiento se puede utilizar específicamente, por ejemplo, para almacenar en caché los resultados:
def get_config(config_cache={}):
if 'data' not in config_cache:
print("Loading configuration...")
config_cache['data'] = "important settings"
return config_cache['data']
print(get_config()) # Cargando configuración... ajustes importantes print(get_config()) # ajustes importantes (ya no se cargan)💡 Nota: Pero en el código real para el almacenamiento en caché, es mejor usar herramientas especiales como functools.lru_cache para que el código sea más claro.
¿Cómo evitar este error?
Solo recuerda una regla simple: nunca uses objetos mutables (listas, diccionarios, conjuntos) como valores predeterminados en los argumentos de una función. Utiliza siempre None y crea el objeto deseado dentro de la función.
Este error es tan común que muchos linters (herramientas de verificación de código) advierten específicamente sobre él. Si estás usando PyCharm, VSCode con pylint o flake8, verás una advertencia cuando intentes escribir este código.
¡Aprende Python correctamente a la primera!
Este y muchos otros temas importantes se pueden estudiar en detalle en Codice - analizar todos los matices con explicaciones claras y consolidarlos en la práctica con tareas interesantes. Cada tema se acompaña de ejemplos de desarrollo real, y las tareas prácticas ayudan a aplicar inmediatamente los conocimientos adquiridos.
Y si durante el proceso de aprendizaje tienes preguntas o quieres discutir puntos complejos, ya tenemos más 2000 personas de ideas afines en activo canal de Telegram, ¡donde siempre te ayudarán a entender y te apoyarán en el camino de la programación!
