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

Недавно я переключился с 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'] 

…?

One Solution collect form web for “Почему словарь не является детерминированным?”


Обновление: в 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'] 
  • Разделение строки на 2-буквенные сегменты
  • Должен ли я кэшировать результаты диапазона, если я их повторно использую?
  • Как я могу улучшить загрузку процессора при использовании модуля многопроцессорности?
  • Угадайте оптимизацию числа игр (пользователь создает номер, компьютерные догадки)
  • Получение исключений, когда исключение уже присутствует в Python 3
  • Преобразование строки в int слишком медленно
  • Как проверить исключения с помощью doctest в Python 2.x и 3.x?
  • Каковы хорошие возможности использования «Аннотации функций» Python3,
  • Python - лучший язык программирования в мире.