Заказаны ли словари в Python 3.6+?


470

Словари упорядочены в Python 3.6 (по крайней мере, под реализацию CPython) в отличие от предыдущих воплощений. Это кажется существенным изменением, но это только короткий параграф в документации . Он описывается как подробность реализации CPython, а не как языковая функция, но также подразумевает, что это может стать стандартом в будущем.

Как новая реализация словаря работает лучше, чем старая при сохранении порядка элементов?

Вот текст из документации:

dict()теперь использует «компактное» представление, впервые разработанное PyPy . Использование памяти новой функцией dict () на 20-25% меньше по сравнению с Python 3.5. PEP 468 (сохранение порядка ** kwargs в функции.) Реализуется этим. Сохраняющий порядок аспект этой новой реализации считается деталью реализации, и на него не следует полагаться (это может измениться в будущем, но желательно иметь эту новую реализацию dict в языке в течение нескольких выпусков, прежде чем изменять спецификацию языка. предписывать семантику сохранения порядка для всех текущих и будущих реализаций Python, это также помогает сохранить обратную совместимость со старыми версиями языка, где все еще действует случайный порядок итераций, например, Python 3.5). (Предоставлено ИНАДА Наоки ввыпуск 27350 . Идея, изначально предложенная Раймондом Хеттингером .)

Обновление в декабре 2017 года: dictсохранение порядка вставки гарантировано для Python 3.7


2
Смотрите эту ветку в списке рассылки Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html, если вы его еще не видели; это в основном дискуссия вокруг этих предметов.
mgc

1
Если теперь предполагается, что kwargs должны быть упорядочены (что является хорошей идеей), а kwargs - это dict, а не OrderedDict, то я думаю, можно предположить, что ключи dict останутся упорядоченными в будущей версии Python, несмотря на то, что в документации сказано иначе.
Дмитрий Синцов

4
@DmitriySintsov Нет, не делайте этого предположения. Эта проблема была поднята во время написания PEP, которая определяет функцию сохранения порядка **kwargsи, как таковая, используемая формулировка является дипломатической: **kwargsв сигнатуре функции теперь гарантированно отображается отображение, сохраняющее порядок вставки . Они использовали термин mapping , чтобы не заставлять никакие другие реализации делать упорядоченный dict (и использовать OrderedDictвнутренне) и как способ показать, что это не должно зависеть от того факта, что dictis не упорядочен.
Димитрис Фасаракис Хиллиард

7
Хорошее видео объяснение от Рэймонда Хеттингера
Алекс

1
@wazoox, порядок и сложность хэш-карты не изменились. Это изменение делает хэш-карту меньше, тратя меньше места, а сэкономленное пространство (обычно?) Больше, чем занимает вспомогательный массив. Быстрее, меньше, заказано - вы можете выбрать все 3.
Джон Ла Рой

Ответы:


513

Заказаны ли словари в Python 3.6+?

Они вставляются по порядку [1] . Начиная с Python 3.6, для реализации Python на CPython словари запоминают порядок вставленных элементов . Это считается деталью реализации в Python 3.6 ; вам нужно использовать, OrderedDictесли вы хотите, чтобы порядок вставки был гарантирован для других реализаций Python (и другого упорядоченного поведения [1] ).

Начиная с Python 3.7 , это больше не деталь реализации, а вместо этого становится языковой особенностью. Из сообщения Python-dev от GvR :

Сделай это так. «Dict сохраняет порядок вставки» - это решение. Спасибо!

Это просто означает, что вы можете зависеть от этого . Другие реализации Python также должны предлагать упорядоченный словарь для вставки, если они хотят быть соответствующей реализацией Python 3.7.


Как 3.6реализация словаря Python работает лучше [2], чем старая, при сохранении порядка элементов?

По сути, сохраняя два массива .

  • Первый массив, dk_entriesсодержит записи ( типаPyDictKeyEntry ) для словаря в том порядке, в котором они были вставлены. Порядок сохранения достигается за счет того, что он является массивом только для добавления, где новые элементы всегда вставляются в конце (порядок вставки).

  • Второй, dk_indicesсодержит индексы для dk_entriesмассива (то есть значения, которые указывают на позицию соответствующей записи в dk_entries). Этот массив действует как хеш-таблица. Когда ключ хэшируется, это приводит к одному из индексов, сохраненных в, dk_indicesи соответствующая запись выбирается посредством индексации dk_entries. Поскольку сохраняются только индексы, тип этого массива зависит от общего размера словаря (в диапазоне от типа int8_t( 1байт) до int32_t/ int64_t( 4/ 8байт) в 32/ 64битных сборках)

В предыдущей реализации должен был размещаться разреженный массив типа PyDictKeyEntryи размера dk_size; к сожалению, это также привело к большому количеству пустого пространства, так как этот массив не мог быть 2/3 * dk_sizeпереполнен по соображениям производительности . (и пустое пространство все еще имело PyDictKeyEntryразмер!).

Сейчас это не так, поскольку сохраняются только необходимые записи (те, которые были вставлены) и сохраняется разреженный массив типа intX_tXзависимости от размера dict) 2/3 * dk_size. Пустое пространство изменено с типа PyDictKeyEntryна intX_t.

Итак, очевидно, что создание разреженного массива типа PyDictKeyEntryтребует гораздо больше памяти, чем разреженный массив для хранения ints.

Вы можете увидеть полный разговор о Python-Dev относительно этой функции, если вам интересно, это хорошее чтение.


В первоначальном предложении Рэймонда Хеттингера можно увидеть визуализацию используемых структур данных, которая отражает суть идеи.

Например, словарь:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

в настоящее время хранится как [keyhash, key, value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Вместо этого данные должны быть организованы следующим образом:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Как вы можете видеть визуально, в исходном предложении много места практически пусто, чтобы уменьшить количество столкновений и ускорить поиск. С новым подходом вы уменьшаете объем требуемой памяти, перемещая разреженность там, где она действительно требуется, в индексах.


[1]: я говорю «вставка упорядочена», а не «упорядочена», так как при наличии OrderedDict «упорядоченный» предполагает дальнейшее поведение, которого не обеспечиваетdict объект . OrderedDicts являются обратимыми, предоставляют чувствительные к порядку методы и, главным образом, предоставляют чувствительные к порядку тесты на равенство ( , ). В настоящее время не предлагается ни одно из этих поведений / методов. ==!=dict


[2]: новые реализации словаря обеспечивают лучшую память , будучи спроектированы более компактно; это главное преимущество здесь. С точки зрения скорости, разница не столь существенна, есть места, где новый дикт может привести к небольшим регрессиям ( например, поиск по ключевым словам), в то время как в других (на ум приходят итерации и изменение размеров) должно наблюдаться повышение производительности.

В целом производительность словаря, особенно в реальных ситуациях, улучшается благодаря введенной компактности.


15
Итак, что происходит, когда элемент удален? это entriesизменяется список? или пустое место сохраняется? или это время от времени сжимается?
njzk2

18
@ njzk2 Когда элемент удаляется, соответствующий индекс заменяется DKIX_DUMMYзначением, -2а запись в entryмассиве заменяется наNULL , когда при вставке новые значения добавляются в массив записей, пока не удалось различить, но довольно точно, когда индексы заполняются за 2/3порог, выполняется изменение размера. Это может привести к сокращению вместо роста, если DUMMYсуществует много записей.
Димитрис Фасаракис Хиллиард

3
@Chris_Rands Нет, единственная реальная регрессия, которую я видел, находится на трекере в сообщении Виктора . Кроме этой микробенчмарки, я не видел никаких других проблем / сообщений, указывающих на серьезную разницу в скорости при реальной рабочей нагрузке. Есть места, где новый dict может вводить небольшие регрессии (например, поиск ключей), в то время как в других (на ум приходит итерация и изменение размера) будет иметь место повышение производительности.
Димитрис Фасаракис Хиллиард

3
Исправление в части изменения размера : словари не меняют размер при удалении элементов, они пересчитывают при повторной вставке. Таким образом, если с помощью dict создается d = {i:i for i in range(100)}и .popвсе элементы без вставки, размер не изменится. Когда вы добавляете к нему снова, d[1] = 1соответствующий размер вычисляется и размер дикта изменяется.
Димитрис Фасаракис Хиллиард

6
@Chris_Rands Я уверен, что он останется. Дело в том, что причина, по которой я изменил свой ответ, чтобы удалить общие утверждения о « dictупорядоченности», dictне упорядочены в том смысле, в каком OrderedDictони. Примечательной проблемой является равенство. dicts имеют порядок, нечувствительный ==, OrderedDicts имеют порядок, чувствительный. Дампы OrderedDictи переходы dictsна сравнение, чувствительные к порядку, могут привести к серьезным сбоям в старом коде. Я предполагаю, что единственное, что может измениться в OrderedDicts, это его реализация.
Димитрис Фасаракис Хиллиард

67

Ниже приводится ответ на первый вопрос:

Должен ли я использовать dictили OrderedDictв Python 3.6?

Я думаю, что это предложение из документации на самом деле достаточно, чтобы ответить на ваш вопрос

Сохраняющий порядок аспект этой новой реализации считается деталью реализации и на него не следует полагаться

dictявно не является упорядоченной коллекцией, поэтому, если вы хотите оставаться последовательным и не полагаться на побочный эффект новой реализации, вам следует придерживаться OrderedDict.

Сделайте свой код будущим :)

Там есть дебаты о том, что здесь .

РЕДАКТИРОВАТЬ: Python 3.7 будет держать это как функцию увидеть


1
Похоже, что если они не имели в виду, что это реальная функция, а только детали реализации, то они не должны даже включать это в документацию.
xji

3
Я не уверен насчет вашей правки. поскольку гарантия распространяется только на Python 3.7, я предполагаю, что рекомендации для Python 3.6 не изменились, то есть диктанты упорядочены в CPython, но не рассчитывают на это
Chris_Rands

25

Обновление: Гвидо ван Россум объявил в списке рассылки, что начиная dictс Python 3.7 во всех реализациях Python должен сохраняться порядок вставки.


2
Теперь, когда порядок ключей является официальным стандартом, какова цель OrderedDict? Или это сейчас избыточно?
Джонни Вафли

2
Я предполагаю, что OrderedDict не будет избыточным, потому что у него есть move_to_endметод, и его равенство чувствительно к порядку: docs.python.org/3/library/… . Смотрите примечание к ответу Джима Фасаракиса Хиллиарда.
FJSJ

@JonnyWaffles см. Ответ Джима и эти вопросы и ответы stackoverflow.com/questions/50872498/…
Chris_Rands

3
Если вы хотите, чтобы ваш код выполнялся одинаково на 2.7 и 3.6 / 3.7 +, вам нужно использовать OrderedDict
лодочный кодер

3
Скорее всего, скоро будет "UnorderedDict" для людей, которые любят поспорить со своими соображениями по соображениям безопасности; p
ZF007

9

Я хотел добавить к обсуждению выше, но не имею репутации, чтобы комментировать.

Python 3.8 еще не совсем выпущен, но он даже будет включать reversed()функцию в словарях (исключая другое отличие от OrderedDict.

Dict и dictviews теперь итерируемы в обратном порядке вставки, используя reversed (). (Предоставлено Rémi Lapeyre в bpo-33462.) Посмотрите, что нового в Python 3.8

Я не вижу упоминаний об операторе равенства или других функциях, OrderedDictпоэтому они не совсем одинаковы.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.