Necesitaba crear una lista de listas en Python, así que escribí lo siguiente:
my_list = [[1] * 4] * 3
La lista se veía así:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Luego cambié uno de los valores más internos:
my_list[0][0] = 5
Ahora mi lista se ve así:
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
que no es lo que quería o esperaba. ¿Puede alguien explicar qué está pasando y cómo solucionarlo?
Solución del problema
Cuando escribes [x]*3
obtienes, esencialmente, la lista [x, x, x]
. Es decir, una lista con 3 referencias al mismo x
. Cuando modifica este sencillo x
, es visible a través de las tres referencias a él:
x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
f"id(l[0]): {id(l[0])}\n"
f"id(l[1]): {id(l[1])}\n"
f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048
x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]
Para solucionarlo, debe asegurarse de crear una nueva lista en cada posición. Una forma de hacerlo es
[[1]*4 for _ in range(3)]
que reevaluará [1]*4
cada vez en lugar de evaluarlo una vez y hacer 3 referencias a 1 lista.
Quizás se pregunte por qué *
no se pueden hacer objetos independientes de la forma en que lo hace la comprensión de listas. Eso es porque el operador de multiplicación *
opera sobre objetos, sin ver expresiones. Cuando usa *
para multiplicar [[1] * 4]
por 3, *
solo ve la lista de 1 elemento [[1] * 4]
evaluada, no el [[1] * 4
texto de la expresión. *
no tiene idea de cómo hacer copias de ese elemento, no tiene idea de cómo reevaluar [[1] * 4]
y no tiene idea de que desea copias y, en general, es posible que ni siquiera haya una forma de copiar el elemento.
La única opción *
que tiene es hacer nuevas referencias a la sublista existente en lugar de intentar hacer nuevas sublistas. Cualquier otra cosa sería inconsistente o requeriría un rediseño importante de las decisiones fundamentales de diseño del lenguaje.
Por el contrario, una lista de comprensión vuelve a evaluar la expresión del elemento en cada iteración. [[1] * 4 for n in range(3)]
reevalúa [1] * 4
cada vez por la misma razón [x**2 for x in range(3)]
reevalúa x**2
cada vez. Cada evaluación de [1] * 4
genera una nueva lista, por lo que la comprensión de la lista hace lo que quería.
Por cierto, [1] * 4
tampoco copia los elementos de [1]
, pero eso no importa, ya que los números enteros son inmutables. No puedes hacer algo como 1.value = 2
y convertir un 1 en un 2.
No hay comentarios.:
Publicar un comentario