GOOGLE ADS

jueves, 28 de abril de 2022

Los cambios en la lista de listas se reflejan inesperadamente en las sublistas

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]*3obtienes, 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]*4cada 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] * 4texto 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] * 4cada vez por la misma razón [x**2 for x in range(3)]reevalúa x**2cada vez. Cada evaluación de [1] * 4genera una nueva lista, por lo que la comprensión de la lista hace lo que quería.

Por cierto, [1] * 4tampoco copia los elementos de [1], pero eso no importa, ya que los números enteros son inmutables. No puedes hacer algo como 1.value = 2y convertir un 1 en un 2.

No hay comentarios.:

Publicar un comentario

Flutter: error de rango al acceder a la respuesta JSON

Estoy accediendo a una respuesta JSON con la siguiente estructura. { "fullName": "FirstName LastName", "listings...