Saltar al contenido
🐍 Curso de Python
/

Módulo 1 · Lección 23 de 59

Rebanado y copias de listas

Cortar trozos de una lista y entender por qué asignarla a otra variable no la copia, con la trampa que eso provoca.

⏱ 12 min de lectura

El rebanado de listas ya lo sabes hacer, porque es el mismo que aprendiste con las cadenas. Esta lección lo repasa en dos minutos y luego dedica el resto a un asunto que confunde a casi todo el mundo y provoca errores muy difíciles de encontrar: asignar una lista a otra variable no la copia.

Entender bien esta diferencia es lo que separa a quien usa listas con soltura de quien de vez en cuando se encuentra con datos cambiados por arte de magia.

Cortar trozos#

La sintaxis es lista[inicio:fin:paso], y el final nunca se incluye:

python
meses = ["ene", "feb", "mar", "abr", "may", "jun"]
#          0      1      2      3      4      5

print(meses[0:3])
print(meses[3:])
print(meses[:3])
print(meses[-2:])
print(meses[::2])
print(meses[::-1])
print(meses[:])
Salida en pantalla
['ene', 'feb', 'mar']
['abr', 'may', 'jun']
['ene', 'feb', 'mar']
['may', 'jun']
['ene', 'mar', 'may']
['jun', 'may', 'abr', 'mar', 'feb', 'ene']
['ene', 'feb', 'mar', 'abr', 'may', 'jun']

Hay una diferencia importante entre un índice y un rebanado, aunque se escriban parecido:

python
meses = ["ene", "feb", "mar"]

print(meses[0], type(meses[0]))
print(meses[0:1], type(meses[0:1]))
Salida en pantalla
ene <class 'str'>
['ene'] <class 'list'>

Un índice te da el elemento. Un rebanado te da siempre una lista, aunque solo contenga un elemento o esté vacía.

Asignar a un rebanado#

Esto sí es nuevo, y solo funciona con listas porque son mutables. Puedes sustituir todo un tramo de golpe:

python
numeros = [1, 2, 3, 4, 5]

numeros[1:3] = [20, 30]
print(numeros)
Salida en pantalla
[1, 20, 30, 4, 5]

Y lo que reemplaza no tiene que medir lo mismo que lo reemplazado: la lista crece o se encoge sola.

python
numeros = [1, 2, 3, 4, 5]

numeros[1:3] = ["a"]              # dos elementos se convierten en uno
print(numeros)

numeros[1:2] = ["x", "y", "z"]    # uno se convierte en tres
print(numeros)

numeros[0:2] = []                 # borrar un tramo
print(numeros)
Salida en pantalla
[1, 'a', 4, 5]
[1, 'x', 'y', 'z', 4, 5]
['y', 'z', 4, 5]

Es una herramienta potente pero poco legible. Úsala con moderación: para borrar un tramo, del numeros[0:2] dice mucho más claro lo que hace.

El asunto central: lista_b = lista_a no copia nada#

Con los números, asignar copia el valor, y eso es lo que tu intuición espera:

python
precio_original = 100
precio_rebajado = precio_original
precio_rebajado = 80

print(precio_original)
print(precio_rebajado)
Salida en pantalla
100
80

Con las listas ocurre otra cosa. Mira con atención, porque esto es lo que hay que aprender de la lección:

python
carrito_ana = ["leche", "pan"]
carrito_luis = carrito_ana        # parece una copia

carrito_luis.append("cerveza")

print(f"Luis: {carrito_luis}")
print(f"Ana:  {carrito_ana}")
Salida en pantalla
Luis: ['leche', 'pan', 'cerveza']
Ana:  ['leche', 'pan', 'cerveza']

Ana no ha comprado cerveza, pero le aparece en el carrito. No hay dos listas: hay una sola lista con dos nombres. La asignación no ha duplicado nada, solo ha añadido una segunda etiqueta pegada al mismo objeto.

Puedes comprobarlo con el operador is, que pregunta si dos nombres apuntan al mismísimo objeto, no si tienen el mismo contenido:

python
carrito_ana = ["leche", "pan"]
carrito_luis = carrito_ana
otro_carrito = ["leche", "pan"]

print(carrito_luis is carrito_ana)      # el mismo objeto
print(otro_carrito is carrito_ana)      # objetos distintos
print(otro_carrito == carrito_ana)      # pero contenido igual
Salida en pantalla
True
False
True

Las cadenas y los números no sufren este problema, y ahora entiendes por qué: son inmutables. Nadie puede cambiarlos por dentro, así que da igual cuántos nombres compartan el mismo objeto. Con las listas, que sí se pueden cambiar por dentro, compartir objeto sí se nota.

Copiar de verdad#

Hay tres formas de crear una lista nueva con los mismos elementos, y las tres son equivalentes:

python
original = ["leche", "pan", "café"]

copia_1 = original[:]          # rebanado completo
copia_2 = list(original)       # el constructor de lista
copia_3 = original.copy()      # el método explícito

copia_1.append("azúcar")
copia_2.remove("pan")
copia_3[0] = "leche desnatada"

print(f"original: {original}")
print(f"copia_1:  {copia_1}")
print(f"copia_2:  {copia_2}")
print(f"copia_3:  {copia_3}")
print(f"¿el mismo objeto? {copia_1 is original}")
Salida en pantalla
original: ['leche', 'pan', 'café']
copia_1:  ['leche', 'pan', 'café', 'azúcar']
copia_2:  ['leche', 'café']
copia_3:  ['leche desnatada', 'pan', 'café']
¿el mismo objeto? False

Ahora sí: cada una va por su cuenta y la original no se ha enterado de nada.

FormaCuándo usarla
original.copy()La más clara. Es la que deberías usar por defecto
list(original)Útil cuando además conviertes algo a lista
original[:]La clásica. La verás en código antiguo y en for x in lista[:]

Por qué la copia es superficial#

Las tres formas de copiar hacen lo que se llama una copia superficial: crean una lista nueva, pero dentro colocan los mismos objetos que había en la original. Con números y cadenas eso no importa, porque son inmutables. Con listas dentro de listas, sí importa.

python
tablero_original = [["-", "-"], ["-", "-"]]
tablero_copia = tablero_original.copy()

tablero_copia[0][0] = "X"

print(f"copia:    {tablero_copia}")
print(f"original: {tablero_original}")
print(f"¿la lista de fuera es la misma? {tablero_copia is tablero_original}")
print(f"¿la primera fila es la misma?   {tablero_copia[0] is tablero_original[0]}")
Salida en pantalla
copia:    [['X', '-'], ['-', '-']]
original: [['X', '-'], ['-', '-']]
¿la lista de fuera es la misma? False
¿la primera fila es la misma?   True

La lista de fuera sí es nueva (is da False), pero las listas de dentro son las mismas (is da True). Al escribir en tablero_copia[0][0] estás entrando en una lista interior que las dos comparten, y el cambio aparece en ambas.

La copia profunda: copy.deepcopy#

Para duplicar también el contenido interior se usa el módulo copy de la biblioteca estándar, que viene con Python y no hay que instalar. Se importa una vez, al principio del fichero:

tablero.py
import copy

tablero_original = [["-", "-"], ["-", "-"]]
tablero_copia = copy.deepcopy(tablero_original)

tablero_copia[0][0] = "X"

print(f"copia:    {tablero_copia}")
print(f"original: {tablero_original}")
print(f"¿la primera fila es la misma? {tablero_copia[0] is tablero_original[0]}")
Salida en pantalla
copia:    [['X', '-'], ['-', '-']]
original: [['-', '-'], ['-', '-']]
¿la primera fila es la misma? False

deepcopy baja hasta el fondo y duplica todas las listas anidadas, por muchos niveles que haya. El precio es que es más lento y gasta más memoria, así que no lo pongas por costumbre: úsalo cuando tengas estructuras anidadas de verdad.

NecesitasUsa
Otro nombre para la misma listab = a
Una lista nueva con elementos simplesb = a.copy()
Una lista nueva con listas dentrob = copy.deepcopy(a)