signo palabras mutables mutabilidad linguistico inmutables inmutable inmutabilidad ejemplos python

python - palabras - Creación de diccionarios con claves y objetos mutables. Una sorpresa



inmutabilidad (3)

En la primera versión, utiliza el mismo objeto de lista vacía como valor para ambas claves, por lo que si cambia una, también cambia la otra.

Mira esto:

>>> empty = [] >>> d = dict.fromkeys(range(2), empty) >>> d {0: [], 1: []} >>> empty.append(1) # same as d[0].append(1) because d[0] references empty! >>> d {0: [1], 1: [1]}

En la segunda versión, se crea un nuevo objeto de lista vacía en cada iteración de la comprensión del dict, por lo que ambos son independientes entre sí.

En cuanto a "por qué" fromkeys() funciona así, bueno, sería sorprendente si no funcionara así. fromkeys(iterable, value) construye un nuevo dictado con claves de iterable que todos tienen el valor value . Si ese valor es un objeto mutable, y usted cambia ese objeto, ¿qué otra cosa podría esperar razonablemente que suceda?

Me encontré con este comportamiento que me sorprendió en Python 2.6 y 3.2:

>>> xs = dict.fromkeys(range(2), []) >>> xs {0: [], 1: []} >>> xs[0].append(1) >>> xs {0: [1], 1: [1]}

Sin embargo, las dict en 3.2 muestran un comportamiento más educado:

>>> xs = {i:[] for i in range(2)} >>> xs {0: [], 1: []} >>> xs[0].append(1) >>> xs {0: [1], 1: []} >>>

¿Por qué los fromkeys comportan así?


Para responder a la pregunta real que se hace: fromkeys comporta así porque no hay otra opción razonable. No es razonable (ni siquiera posible) que las fromkeys decidan si su argumento es mutable o no y hacer copias nuevas cada vez. En algunos casos no tiene sentido, y en otros es simplemente imposible.

El segundo argumento que pasa es, por lo tanto, solo una referencia, y se copia como tal. Una asignación de [] en Python significa "una sola referencia a una nueva lista", no "hacer una nueva lista cada vez que accedo a esta variable". La alternativa sería pasar una función que genere nuevas instancias, que es la funcionalidad que le suministran las comprensiones.

Aquí hay algunas opciones para crear múltiples copias reales de un contenedor mutable:

  1. Como mencionó en la pregunta, las comprensiones de dictados le permiten ejecutar una declaración arbitraria para cada elemento:

    d = {k: [] for k in range(2)}

    Lo importante aquí es que esto es equivalente a poner la asignación k = [] en un bucle for . Cada iteración crea una nueva lista y la asigna a un valor.

  2. Use la forma del constructor dict sugerido por @Andrew Clark :

    d = dict((k, []) for k in range(2))

    Esto crea un generador que nuevamente hace la asignación de una nueva lista a cada par clave-valor cuando se ejecuta.

  3. Use un collections.defaultdict lugar de un dict regular:

    d = collections.defaultdict(list)

    Esta opción es un poco diferente de las otras. En lugar de crear las nuevas referencias de la lista por adelantado, defaultdict llamará a la list cada vez que acceda a una clave que aún no esté allí. Por lo tanto, puede agregar las claves tan perezosamente como quiera, lo que puede ser muy conveniente a veces:

    for k in range(2): d[k].append(42)

    Ya que ha configurado la fábrica para nuevos elementos, esto realmente se comportará exactamente como esperaba que las fromkeys comportaran en la pregunta original.

  4. Utilice dict.setdefault cuando acceda a claves potencialmente nuevas. Esto hace algo similar a lo que hace defaultdict , pero tiene la ventaja de ser más controlado, en el sentido de que solo el acceso que desea crear nuevas claves realmente las crea:

    d = {} for k in range(2): d.setdefault(k, []).append(42)

    La desventaja es que se crea un nuevo objeto de lista vacía cada vez que llama a la función, incluso si nunca se asigna a un valor. Este no es un gran problema, pero podría acumularse si lo llama con frecuencia y / o su contenedor no es tan simple como la list .


Su ejemplo de Python 2.6 es equivalente a lo siguiente, lo que puede ayudar a aclarar:

>>> a = [] >>> xs = dict.fromkeys(range(2), a)

Cada entrada en el diccionario resultante tendrá una referencia al mismo objeto. Los efectos de mutar ese objeto serán visibles a través de cada entrada de dictado, como has visto, porque es un objeto.

>>> xs[0] is a and xs[1] is a True

Use una comprensión de dictado, o si está atrapado en Python 2.6 o más y no tiene comprensión de diccionario, puede obtener el comportamiento de comprensión de dict() utilizando dict() con una expresión generadora:

xs = dict((i, []) for i in range(2))