• Saltar al contenido principal
  • Skip to secondary menu
  • Saltar a la barra lateral principal
  • Saltar al pie de página
  • Inicio
  • Secciones
    • Ciencia de datos
    • Criptografía
    • Herramientas
    • Machine Learning
    • Noticias
    • Opinión
    • Productividad
    • Programación
      • JavaScript
      • Julia
      • Matlab
      • Python
      • R
  • Programación
    • JavaScript
    • Julia
    • Matlab
    • Python
    • R
  • Laboratorio
    • Estadística
      • Calculadora del Tamaño Muestral en Encuestas
      • Calculadora de estadísticos descriptivos
      • Test de normalidad
      • Calculadora de contrastes de hipotesis
      • Calculadora de tamano del efecto
      • Simulador de Regresión Lineal con Ruido
      • Visualizador de PCA
      • Visualizador de Series Temporales
      • Simulador de Regresión Logística
      • Simulador de K-Means
      • Simulador de DBSCAN
      • Detector de la Ley de Benford
      • Ajuste de Curvas
      • Calculadora de Matrices
    • Probabilidad
      • Calculadora de Probabilidad de Distribuciones
      • Calculadora de Probabilidades de Lotería
      • Simulador del Problema de Monty Hall
      • Simulador de la Estrategia Martingala
    • Finanzas
      • Calculadora de Préstamos e Hipotecas
      • Conversor TIN ↔ TAE
      • Calculadora DCA con ajuste por inflación
      • Calculadora XIRR con Flujos Irregulares
      • Simulador FIRE (Financial Independence, Retire Early)
    • Negocios
      • CLV
      • Scoring
    • Herramientas
      • Formateador / Minificador de JSON
      • Conversor CSV ↔ JSON
      • Comparador y Formateador de Texto y JSON
      • Formateador y Tester de Expresiones Regulares
      • Inspector de JWT
      • Generador y verificador de hashes
      • Codificador / Decodificador Base64 y URL
      • Conversor de bases numericas
      • Conversor de Timestamp Unix
      • Conversor de colores
      • Generador de UUIDs
    • Juegos
      • Tres en Raya
      • Nim con Q-Learning
    • Más
      • Método D’Hondt
      • Generador de Contraseñas Seguras
  • Noticias
  • Boletín
  • Contacto
  • Tienda
    • Libros
    • Equipamiento de oficina
    • Equipamiento en movilidad

Analytics Lane

Ciencia e ingeniería de datos aplicada

  • Ciencia de datos
  • Machine Learning
  • IA Generativa
  • Python
  • Pandas
  • NumPy
  • R
  • Excel

Shallow copy vs Deep copy en Python: la mutación inesperada

noviembre 10, 2026 Por Daniel Rodríguez Deja un comentario
Tiempo de lectura: 7 minutos

Imagina que tienes una lista con datos de un usuario:

usuario_original = {
    'nombre': 'Juan',
    'edad': 25,
    'direccion': {
        'calle': 'Gran Vía',
        'ciudad': 'Madrid'
    }
}

Ahora quieres hacer una copia para trabajar sin afectar el original:

usuario_copia = usuario_original.copy()

# Cambias la copia
usuario_copia['nombre'] = 'Carlos'

print(usuario_original['nombre'])  # Juan
print(usuario_copia['nombre'])     # Carlos

# Bien, la copia no afectó al original

Pero ahora cambias la ciudad en la copia:

usuario_copia['direccion']['ciudad'] = 'Barcelona'

print(usuario_original['direccion']['ciudad'])  # Barcelona
print(usuario_copia['direccion']['ciudad'])     # Barcelona

# ¡¿QUÉ?! El original también cambió!

Acabas de encontrar posiblemente el bug más silencioso de Python: el shallow copy. Tu código “parece” correcto. Hiciste una copia. Pero cambió el original de todas formas.

La razón: .copy() hace una copia superficial. Copia el contenedor, pero no lo que hay dentro. Si hay estructuras anidadas (listas dentro de listas, diccionarios dentro de diccionarios), seguirán apuntando a los MISMOS objetos. Cambias uno, cambias ambos.

En esta entrada vamos a ver:

  • La diferencia entre shallow y deep copy.
  • Por qué shallow copy te traiciona (con estructuras anidadas).
  • Cuándo cada uno es suficiente.
  • Cómo evitar este bug.
  • Deep copy y sus costes (es más lento).

Tabla de contenidos

  • 1. La diferencia visual
    • 1.1. Shallow copy
    • 1.2. Deep copy
  • 2. El problema: shallow copy con estructuras anidadas
    • 2.1. Ejemplo 1: Diccionarios anidados
    • 2.2. Ejemplo 2: Listas dentro de listas
    • 2.3. Ejemplo 3: Listas de diccionarios
  • 3. Cómo hacer una copia VERDADERA: deep copy
  • 4. Diferencia con tuplas (sorpresa)
  • 5. Métodos de copia: cuál usar en cada caso
    • 5.1. Shallow copy
    • 5.2. Deep copy
  • 6. Cuándo cada uno es suficiente
    • 6.1. Usa shallow copy cuando:
    • 6.2. Usa deep copy cuando:
  • 7. El coste: deep copy es más lento
  • 8. Ejemplos prácticos
    • 8.1. Ejemplo 1: Configuración de usuario
    • 8.2. Ejemplo 2: Pipeline de transformación de datos
    • 8.3. Ejemplo 3: Clonación de objeto complejos
    • 8.4. Ejemplo 4: Shallow copy es suficiente aquí
  • 9. Debugging: cómo encontrar este bug
  • 10. Operaciones que usan shallow copy
  • 11. La verdad incómoda: = tampoco copia
  • 12. Tabla resumen
  • 13. Conclusiones

La diferencia visual

Imagina una estructura anidada como una caja dentro de otra caja:

Cómo Proyectar Cuándo Alcanzarás la Independencia Financiera
En Analytics Lane
Cómo Proyectar Cuándo Alcanzarás la Independencia Financiera

datos = {
    'nivel1': [1, 2, {'nivel2': 'valor'}]
}

Shallow copy

datos (original)
├── 'nivel1' → lista A
    ├── 1
    ├── 2
    └── dict B → {'nivel2': 'valor'}

datos_copia = datos.copy()
├── 'nivel1' → lista A (¡MISMA referencia!)
    ├── 1
    ├── 2
    └── dict B → {'nivel2': 'valor'} (¡MISMA referencia!)

Shallow copy copia las “cajas exteriores” pero los contenidos anidados siguen siendo los MISMOS objetos. Modificas el contenido anidado en la copia y afectas al original.

Deep copy

datos (original)
├── 'nivel1' → lista A
    ├── 1
    ├── 2
    └── dict B → {'nivel2': 'valor'}

datos_copia = deepcopy(datos)
├── 'nivel1' → lista A' (NUEVA copia)
    ├── 1
    ├── 2
    └── dict B' → {'nivel2': 'valor'} (NUEVA copia)

Deep copy recorre toda la estructura y copia TODO, incluso los objetos anidados. Ahora sí son completamente independientes.

El problema: shallow copy con estructuras anidadas

Ejemplo 1: Diccionarios anidados

usuario_original = {
    'nombre': 'Juan',
    'datos': {
        'edad': 25,
        'ciudad': 'Madrid'
    }
}

# Shallow copy
usuario_copia = usuario_original.copy()

# Modificas la copia
usuario_copia['datos']['ciudad'] = 'Barcelona'

# ¡El original cambió!
print(usuario_original['datos']['ciudad'])  # Barcelona
print(usuario_copia['datos']['ciudad'])     # Barcelona

¿Por qué? Porque copy() solo copia el diccionario exterior. La clave 'datos' sigue apuntando al MISMO diccionario anidado en ambos.

print(id(usuario_original['datos']))  # 140234567891648
print(id(usuario_copia['datos']))     # 140234567891648 (¡MISMA dirección!)

Ejemplo 2: Listas dentro de listas

matriz_original = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Shallow copy
matriz_copia = matriz_original.copy()

# Modificas la copia
matriz_copia[0][0] = 999

# ¡La original también cambió!
print(matriz_original[0][0])  # 999
print(matriz_copia[0][0])     # 999

Las listas interiores son las MISMAS en ambas.

Ejemplo 3: Listas de diccionarios

tareas_original = [
    {'id': 1, 'titulo': 'Tarea 1', 'completada': False},
    {'id': 2, 'titulo': 'Tarea 2', 'completada': False},
]

# Shallow copy
tareas_copia = tareas_original.copy()

# Marcar como completada en la copia
tareas_copia[0]['completada'] = True

# ¡La original también se marcó!
print(tareas_original[0]['completada'])  # True
print(tareas_copia[0]['completada'])     # True

Cómo hacer una copia VERDADERA: deep copy

Necesitas importar deepcopy:

from copy import deepcopy

usuario_original = {
    'nombre': 'Juan',
    'datos': {
        'edad': 25,
        'ciudad': 'Madrid'
    }
}

# Deep copy: copia TODO recursivamente
usuario_copia = deepcopy(usuario_original)

# Ahora sí puedes modificar sin afectar el original
usuario_copia['datos']['ciudad'] = 'Barcelona'

print(usuario_original['datos']['ciudad'])  # Madrid
print(usuario_copia['datos']['ciudad'])     # Barcelona

Ahora sí son completamente independientes:

print(id(usuario_original['datos']))  # 140234567891648
print(id(usuario_copia['datos']))     # 140234567991712 (¡DIFERENTE!)

Diferencia con tuplas (sorpresa)

Las tuplas tienen un comportamiento raro:

tupla_original = (1, 2, [3, 4, 5])

# "Copiar" una tupla
tupla_copia = tupla_original

# Modificar la lista dentro
tupla_copia[2][0] = 999

print(tupla_original)  # (1, 2, [999, 4, 5]) cambió!

¿Por qué? Porque las tuplas son inmutables pero contienen referencias. No puedes cambiar la tupla, pero sí lo que hay dentro.

# Intentar cambiar la tupla misma
tupla_copia[0] = 10
# TypeError: 'tuple' object does not support item assignment

# Pero cambiar el contenido... funciona
tupla_copia[2][0] = 999  # Esto sí

Métodos de copia: cuál usar en cada caso

Shallow copy

# Diccionario
d = {'a': 1, 'b': [2, 3]}
d_copy = d.copy()
d_copy2 = dict(d)  # También funciona

# Lista
l = [1, 2, [3, 4]]
l_copy = l.copy()
l_copy2 = l[:]     # Slicing también copia
l_copy3 = list(l)  # Constructor también

# Set
s = {1, 2, 3}
s_copy = s.copy()

# Tupla (no se copia, son immutables)
t = (1, 2, 3)
t_copy = t  # No necesitas copiar

Deep copy

from copy import deepcopy

# Cualquier estructura
d = {'a': 1, 'b': [2, {'c': 3}]}
d_deep = deepcopy(d)

l = [1, [2, [3, 4]]]
l_deep = deepcopy(l)

t = (1, [2, 3], {'a': 4})
t_deep = deepcopy(t)

Cuándo cada uno es suficiente

Usa shallow copy cuando:

  • Los datos son simples y sin nesting: solo strings, números, booleanos.
  • NO hay listas, diccionarios o objetos dentro.
  • Solo quieres una “copia de seguridad” del nivel superior.
# Shallow copy es suficiente
datos = {
    'nombre': 'Juan',
    'edad': 25,
    'activo': True
}

copia = datos.copy()
copia['nombre'] = 'Carlos'  # Solo cambias valores simples
# No afecta al original

Usa deep copy cuando:

  • Hay estructuras anidadas: listas dentro de listas, diccionarios dentro de diccionarios.
  • Vas a modificar el contenido anidado en la copia.
  • Necesitas verdadera independencia entre original y copia.
# Deep copy es necesario
datos = {
    'usuario': 'Juan',
    'perfil': {
        'edad': 25,
        'hobbies': ['lectura', 'deportes']
    }
}

copia = deepcopy(datos)
copia['perfil']['hobbies'].append('música')  # No afecta al original

El coste: deep copy es más lento

Deep copy recorre toda la estructura recursivamente, por lo que es necesariamente más lento que una copia normal.

import timeit
from copy import deepcopy

datos = {'nivel1': {'nivel2': {'nivel3': {'nivel4': list(range(1000))}}}}

# Shallow copy
tiempo_shallow = timeit.timeit(
    lambda: datos.copy(),
    number=100000
)

# Deep copy
tiempo_deep = timeit.timeit(
    lambda: deepcopy(datos),
    number=100000
)

print(f"Shallow: {tiempo_shallow:.4f}s")  # ~0.01s
print(f"Deep:    {tiempo_deep:.4f}s")     # ~0.5s (50 veces más lento!)

Para estructuras profundas, deep copy puede ser notablemente más lento. Úsalo solo cuando lo necesites.

Ejemplos prácticos

Ejemplo 1: Configuración de usuario

# Template de configuración
config_template = {
    'tema': 'oscuro',
    'lenguaje': 'es',
    'notificaciones': {
        'email': True,
        'push': False,
        'sms': False
    }
}

# Crear configuración para cada usuario
def crear_config_usuario(user_id):
    from copy import deepcopy

    # Necesita deep copy porque modificaremos notificaciones
    config = deepcopy(config_template)

    # Personalizar sin afectar el template
    config['notificaciones']['email'] = False

    return config

config1 = crear_config_usuario(1)
config2 = crear_config_usuario(2)

# Cada una es independiente
config1['notificaciones']['email'] = False
print(config2['notificaciones']['email'])  # True (no cambió)

Si usases copy() en lugar de deepcopy(), cambiar notificaciones en config1 afectaría a config2.

Ejemplo 2: Pipeline de transformación de datos

from copy import deepcopy

def procesar_datos(datos_originales):
    # Necesitas deep copy para no afectar los originales
    datos = deepcopy(datos_originales)

    # Transformación 1
    for item in datos:
        item['procesado'] = True
        item['timestamp'] = "2024-01-15"

    # Transformación 2
    datos_filtrados = [d for d in datos if d.get('activo')]

    return datos_filtrados, datos

originales = [
    {'id': 1, 'activo': True, 'datos': {'valor': 100}},
    {'id': 2, 'activo': False, 'datos': {'valor': 200}},
]

procesados, transformados = procesar_datos(originales)

print(originales[0])  # Sin 'procesado' ni 'timestamp'
print(procesados[0])  # Con 'procesado' y 'timestamp'

Con shallow copy, los cambios en item['datos']['valor'] afectarían a los originales.

Ejemplo 3: Clonación de objeto complejos

from copy import deepcopy

class Proyecto:
    def __init__(self, nombre, tareas):
        self.nombre = nombre
        self.tareas = tareas  # lista de diccionarios

proyecto1 = Proyecto("App", [
    {'titulo': 'Diseño', 'completada': False},
    {'titulo': 'Backend', 'completada': False},
])

# Crear una copia sin afectar el original
proyecto2 = deepcopy(proyecto1)
proyecto2.nombre = "Web"
proyecto2.tareas[0]['completada'] = True

print(proyecto1.tareas[0]['completada'])  # False
print(proyecto2.tareas[0]['completada'])  # True

Ejemplo 4: Shallow copy es suficiente aquí

# Datos simples sin nesting
coordenadas_originales = {
    'x': 10,
    'y': 20,
    'nombre': 'punto_a'
}

# Shallow copy está bien
coordenadas_copia = coordenadas_originales.copy()

coordenadas_copia['x'] = 15
coordenadas_copia['nombre'] = 'punto_b'

# El original no cambió
print(coordenadas_originales['x'])  # 10
print(coordenadas_originales['nombre'])  # punto_a

Debugging: cómo encontrar este bug

Síntoma: cambias algo en la “copia” y el original también cambia. Sin razón aparente.

Solución: busca .copy() en el código. Pregúntate: “¿hay estructuras anidadas?” Si sí, cámbialo a deepcopy().

# Probablemente un bug
lista_copia = lista_original.copy()

# Cambias algo anidado
lista_copia[0]['campo'] = "nuevo valor"

# Y el original cambió
print(lista_original[0]['campo'])  # "nuevo valor"

# Arreglado
from copy import deepcopy
lista_copia = deepcopy(lista_original)

# Ahora el original no cambia

Operaciones que usan shallow copy

Ten cuidado: muchas operaciones hacen shallow copy “silenciosamente”:

from copy import deepcopy

datos = {'a': [1, 2, 3]}

# Todas estas son shallow copies
copia1 = datos.copy()
copia2 = dict(datos)
copia3 = {**datos}  # Unpacking
copia4 = datos[:]  # Slicing (solo para listas)

# Todas comparten la lista interna
copia1['a'].append(999)
print(datos)  # {'a': [1, 2, 3, 999]} ← cambió!

Si hay nesting, todas fallan de la misma forma. Usa deepcopy().

La verdad incómoda: = tampoco copia

Una trampa extra:

datos_original = {'a': [1, 2, 3]}
datos_copia = datos_original  # Esto NO es una copia

datos_copia['a'].append(999)

print(datos_original)  # {'a': [1, 2, 3, 999]} ← ¡cambió!

= no copia nada. Solo crea un alias. Ambas variables apuntan al MISMO objeto.

Tabla resumen

MétodoQué copiaCuándo usarVelocidad
=Nada (alias)Nunca (intención clara)N/A
.copy()Solo nivel superior (shallow)Datos sin nestingRápido
[:] (slicing)Solo nivel superior (shallow)Listas sin nestingRápido
deepcopy()TODO recursivamenteEstructuras anidadasLento

Conclusiones

Shallow copy y deep copy parecen lo mismo (“copiar una estructura”), pero son fundamentalmente distintos:

  • Shallow copy (copy(), [:], unpacking): copia el contenedor, no lo que hay dentro. Las estructuras anidadas siguen siendo REFERENCIAS al original.
  • Deep copy (deepcopy()): recorre toda la estructura y copia TODO, incluso lo anidado. Verdadera independencia.

El bug ocurre silenciosamente: tu código “funciona” hasta que modificas algo anidado. Ahí descubres que ambas estructuras comparten ese contenido.

Regla simple:

  • ¿Hay listas, diccionarios u objetos DENTRO? → usa deepcopy().
  • ¿Solo datos simples (strings, números)? → copy() está bien.
  • ¿No estás seguro? → usa deepcopy() (es más lento, pero correcto siempre).

Imagen de Jill Wellington en Pixabay

¿Te ha parecido de utilidad el contenido?

¡Puntúalo entre una y cinco estrellas!

Puntuación promedio 0 / 5. Votos emitidos: 0

Ya que has encontrado útil este contenido...

¡Síguenos en redes sociales!

¡Siento que este contenido no te haya sido útil!

¡Déjame mejorar este contenido!

Dime, ¿cómo puedo mejorar este contenido?

Publicaciones relacionadas

  • Cómo Proyectar Cuándo Alcanzarás la Independencia Financiera
  • Cómo Saber si una Diferencia Significativa es Importante
  • Cómo Elegir el Test de Hipótesis Correcto para tus Datos
  • Qué significa realmente “calidad de datos”
  • Cómo Detectar Pseudo-IDs y Data Leakage en el Constructor de Scorecards
  • Tuplas vs Listas en Python: parecen lo mismo, pero no
  • == vs is en Python: la diferencia que causa bugs misteriosos
  • Curiosidad: El puerto 80 y los números de puerto

Publicidad

Publicado en: Python

Interacciones con los lectores

Deja una respuesta Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

I accept the Terms and Conditions and the Privacy Policy

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Barra lateral principal

Suscríbete a nuestro boletín

Suscríbete al boletín semanal para estar al día de todas las publicaciones.

Política de Privacidad

Analytics Lane en redes sociales

  • Amazon
  • Bluesky
  • Facebook
  • GitHub
  • Instagram
  • Mastodon
  • Pinterest
  • RSS
  • Telegram
  • Tumblr
  • Twitter
  • YouTube

Publicidad

Entradas recientes

Shallow copy vs Deep copy en Python: la mutación inesperada

noviembre 10, 2026 Por Daniel Rodríguez

Cómo Saber si una Diferencia Significativa es Importante

noviembre 5, 2026 Por Daniel Rodríguez

== vs is en Python: la diferencia que causa bugs misteriosos

noviembre 3, 2026 Por Daniel Rodríguez

Publicidad

Es tendencia

  • Método del codo (Elbow method) para seleccionar el número óptimo de clústeres en K-means publicado el junio 9, 2023 | en Ciencia de datos
  • Función de interpolación lineal en Excel sin VBA publicado el noviembre 3, 2021 | en Herramientas
  • Gini, KS y AUC: Cómo Medir la Calidad de un Modelo de Credit Scoring publicado el agosto 18, 2026 | en Ciencia de datos
  • Gráficos de barras en Matplotlib publicado el julio 5, 2022 | en Python
  • Los mejores conjuntos de datos para Machine Learning publicado el septiembre 11, 2024 | en Ciencia de datos, Reseñas

Publicidad

Lo mejor valorado

4.9 (24)

Seleccionar filas y columnas en Pandas con iloc y loc

4.6 (16)

Archivos JSON con Python: lectura y escritura

4.4 (14)

Ordenación de diccionarios en Python mediante clave o valor

4.7 (13)

Operaciones de filtrado de DataFrame con Pandas en base a los valores de las columnas

4.9 (11)

Pandas: Cambiar los tipos de datos en los DataFrames

Comentarios recientes

  • Daniel Rodríguez en Curiosidad: La Paradoja de Simpson, o por qué no siempre debes fiarte de los promedios
  • Hatepi en Curiosidad: La Paradoja de Simpson, o por qué no siempre debes fiarte de los promedios
  • Daniel Rodríguez en Los récords con asterisco, o la épica del titular sin contexto – El bestiario de los indicadores económicos absurdos (parte 8 y final)
  • Juan en Los récords con asterisco, o la épica del titular sin contexto – El bestiario de los indicadores económicos absurdos (parte 8 y final)
  • bif en JSON en bases de datos: cuándo es buena idea y cuándo no

Publicidad


Footer

Analytics Lane

  • Acerca de Analytics Lane
  • Boletín de noticias
  • Contacto
  • Libros
  • Lo más popular
  • Noticias
  • Tienda
  • Tiendas afiliadas

Secciones

  • Ciencia de datos
  • Criptografía
  • Herramientas
  • Machine Learning
  • Opinión
  • Productividad
  • Programación
  • Reseñas

Sobre de Analytics Lane

En Analytics Lane tratamos de explicar los principales conceptos de la ciencia e ingeniería de datos con un enfoque práctico. Los principales temas tratados son ciencia de datos, ingeniería de datos, inteligencia artificial, machine learning, deep learning y criptografía. Además, también se habla de los principales lenguajes de programación y herramientas utilizadas por los científicos e ingenieros de datos.

Copyright © 2018-2026 Analytics Lane ·Términos y condiciones ·Política de Cookies ·Política de Privacidad ·Herramientas de privacidad ·Contacto