Почему словарь не является детерминированным?

Недавно я переключился с Python 2.7 на Python 3.3, и кажется, что в Python 2 упорядочение ключей словаря было произвольным, но последовательным, в Python 3 упорядочение ключей словаря, полученного с помощью, например, vars() выглядит недетерминированным.

Если я запустил:

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

как в Python 2.7, так и в Python 3.3, тогда:

  • Python 2.7 последовательно дает мне

     ['__dict__', '__module__', '__weakref__', '__doc__'] 
  • С Python 3.3 я могу получить любой случайный порядок – например:

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

Откуда этот детерминизм? И почему что-то вроде

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

… согласуется между циклами, всегда давая

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

…?


Обновление: в Python 3.6 у dict есть новая реализация, которая сохраняет порядок вставки. Однако это детализация реализации, и на нее нельзя положиться.


Это результат исправления безопасности с 2012 года, которое по умолчанию было включено в Python 3.3 (прокрутите вниз до «Улучшения безопасности»).

Из объявления:

Рандомизация хеширования приводит к тому, что порядок итераций диктов и множеств непредсказуем и различается в рамках Python. Python никогда не гарантировал порядок итераций ключей в dict или set, и приложениям рекомендуется никогда не полагаться на него. Исторически, порядок итераций dict не очень часто менялся в разных версиях и всегда оставался согласованным между последовательными запусками Python. Таким образом, некоторые существующие приложения могут полагаться на dict или set ordering. Из-за этого и того факта, что многие приложения Python, которые не принимают ненадежный ввод, не уязвимы для этой атаки, во всех стабильных версиях Python, упомянутых здесь, HASH RANDOMIZATION ОТКЛЮЧЕН ПО УМОЛЧАНИЮ.

Как отмечено выше, последний, заглавный бит больше недействителен в Python 3.3.

См. Также: документация object.__hash__() (боковая панель «Примечание»).

Если это абсолютно необходимо, вы можете отключить хеш-рандомизацию в версиях Python, затронутых этим поведением, установив PYTHONHASHSEED среды PYTHONHASHSEED в 0 .


Ваш контрпример:

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

… на самом деле не всегда дает тот же результат в Python 3.3, хотя количество разных порядков ограничено из-за того, как обрабатываются хэш-коллизии:

 $ 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'] 

Как отмечалось в начале этого ответа, это уже не так в 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'] 
Interesting Posts