dict data python dictionary python-3.x python-3.3 non-deterministic

python - data - ¿Por qué el ordenamiento del diccionario no es determinista?



python dict put (1)

Recientemente cambié de Python 2.7 a Python 3.3, y parece que mientras en Python 2 el orden de las teclas del diccionario era arbitrario pero consistente, en Python 3 el orden de las claves de un diccionario obtenido con, por ejemplo, vars() parece no determinista.

Si corro:

class Test(object): pass parameters = vars(Test) print(list(parameters.keys()))

en Python 2.7 y Python 3.3, entonces:

  • Python 2.7 constantemente me da

    [''__dict__'', ''__module__'', ''__weakref__'', ''__doc__'']

  • Con Python 3.3, puedo obtener cualquier orden aleatorio, por ejemplo:

    [''__weakref__'', ''__module__'', ''__qualname__'', ''__doc__'', ''__dict__''] [''__doc__'', ''__dict__'', ''__qualname__'', ''__module__'', ''__weakref__''] [''__dict__'', ''__module__'', ''__qualname__'', ''__weakref__'', ''__doc__''] [''__weakref__'', ''__doc__'', ''__qualname__'', ''__dict__'', ''__module__'']

¿De dónde viene este no determinismo? Y por qué es algo así

list({str(i): i for i in range(10)}.keys())

... consistente entre carreras, siempre dando

[''3'', ''2'', ''1'', ''0'', ''7'', ''6'', ''5'', ''4'', ''9'', ''8'']

...?


Actualización: en Python 3.6, dict tiene una nueva implementación que preserva el orden de inserción. Sin embargo, este es un detalle de implementación y no se debe confiar en él.

Este es el resultado de una solución de seguridad de 2012, que se habilitó por defecto en Python 3.3 (baje hasta "Mejoras de seguridad").

Del anuncio:

La aleatorización de hash hace que el orden de iteración de los dicts y los conjuntos sea impredecible y difiera entre las ejecuciones de Python. Python nunca ha garantizado el orden de iteración de las claves en un dict o conjunto, y se aconseja a las aplicaciones que nunca confíen en él. Históricamente, el orden de iteración dict no ha cambiado muy a menudo a través de los lanzamientos y siempre ha sido uniforme entre las ejecuciones sucesivas de Python. Por lo tanto, algunas aplicaciones existentes pueden depender de dict o establecer el orden. Debido a esto y al hecho de que muchas aplicaciones Python que no aceptan entradas que no son de confianza no son vulnerables a este ataque, en todas las versiones estables de Python mencionadas aquí, HASH RANDOMIZATION ESTÁ DESACTIVADA POR DEFAULT.

Como se señaló anteriormente, el último bit en mayúscula ya no es verdadero en Python 3.3.

Consulte también: object.__hash__() documentation ("Note" sidebar).

Si es absolutamente necesario, puede deshabilitar la asignación aleatoria de hash en las versiones de Python afectadas por este comportamiento estableciendo la variable de entorno PYTHONHASHSEED en 0 .

Su contraejemplo:

list({str(i): i for i in range(10)}.keys())

... de hecho, nunca da el mismo resultado en Python 3.3, aunque el número de ordenamientos diferentes es limitado debido a la forma en que se manejan las colisiones hash:

$ for x in {0..999} > do > python3.3 -c "print(list({str(i): i for i in range(10)}.keys()))" > done | sort | uniq -c 61 [''0'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7'', ''8'', ''9''] 73 [''1'', ''0'', ''3'', ''2'', ''5'', ''4'', ''7'', ''6'', ''9'', ''8''] 62 [''2'', ''3'', ''0'', ''1'', ''6'', ''7'', ''4'', ''5'', ''8'', ''9''] 59 [''3'', ''2'', ''1'', ''0'', ''7'', ''6'', ''5'', ''4'', ''9'', ''8''] 58 [''4'', ''5'', ''6'', ''7'', ''0'', ''1'', ''2'', ''3'', ''8'', ''9''] 55 [''5'', ''4'', ''7'', ''6'', ''1'', ''0'', ''3'', ''2'', ''9'', ''8''] 62 [''6'', ''7'', ''4'', ''5'', ''2'', ''3'', ''0'', ''1'', ''8'', ''9''] 63 [''7'', ''6'', ''5'', ''4'', ''3'', ''2'', ''1'', ''0'', ''9'', ''8''] 60 [''8'', ''9'', ''0'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7''] 66 [''8'', ''9'', ''2'', ''3'', ''0'', ''1'', ''6'', ''7'', ''4'', ''5''] 65 [''8'', ''9'', ''4'', ''5'', ''6'', ''7'', ''0'', ''1'', ''2'', ''3''] 53 [''8'', ''9'', ''6'', ''7'', ''4'', ''5'', ''2'', ''3'', ''0'', ''1''] 62 [''9'', ''8'', ''1'', ''0'', ''3'', ''2'', ''5'', ''4'', ''7'', ''6''] 52 [''9'', ''8'', ''3'', ''2'', ''1'', ''0'', ''7'', ''6'', ''5'', ''4''] 73 [''9'', ''8'', ''5'', ''4'', ''7'', ''6'', ''1'', ''0'', ''3'', ''2''] 76 [''9'', ''8'', ''7'', ''6'', ''5'', ''4'', ''3'', ''2'', ''1'', ''0'']

Como se señaló al principio de esta respuesta, ya no es el caso en Python 3.6:

$ for x in {0..999} > do > python3.6 -c "print(list({str(i): i for i in range(10)}.keys()))" > done | sort | uniq -c 1000 [''0'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7'', ''8'', ''9'']