Словари упорядочены в Python 3.6+?

avatar
Chris_Rands
11 октября 2016 в 14:59
201114
5
633

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

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

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

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

Обновление, декабрь 2017 г.: dicts сохранение порядка размещения гарантировано для Python 3.7

Источник
mgc
11 октября 2016 в 15:11
5

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

Chris_Rands
9 декабря 2016 в 14:21
0

Информация здесь от Рэймона Хеттингера, включая рецепт исходного кода для нового dict. Интересно, что он говорит: «В то время, когда это было представлено, настроение было против заказа диктов, поэтому этот [оригинальный] рецепт намеренно заполняет удаленные значения последней записью в списке».

Dmitriy Sintsov
12 января 2017 в 12:32
2

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

Dimitris Fasarakis Hilliard
4 февраля 2017 в 17:18
6

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

Alex
22 июля 2017 в 16:38
11

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

John La Rooy
8 августа 2017 в 20:28
1

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

martineau
3 июня 2019 в 09:36
0

Любой способ автоматически преобразовать OrderedDict в обычные dict в Python 3.7, или нужно переключаться вручную, проверяя, какая версия Python работает?

Chris_Rands
3 июня 2019 в 10:04
1

@martineau Возможно, стоит отдельного вопроса, но я не знаю. Я думаю, что выигрыш в производительности от переключения будет незначительным. Кроме того, вам может понадобиться OrderedDict, даже в Python 3.7 coderhelper.com/questions/50872498/…

martineau
3 июня 2019 в 10:12
0

Крис: Хорошие моменты в связанном ответе. Я думаю, что существует большое количество вариантов использования OrderedDict, в которых не используются эти «расширенные» функции — поэтому я и спросил — но достаточно просто проверить, какая версия Python используется, и выбрать, какую из них вы хотите, когда они могут использоваться взаимозаменяемо.

Ответы (5)

avatar
Dimitris Fasarakis Hilliard
11 октября 2016 в 15:17
707

Упорядочиваются ли словари в 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.


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

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

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

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

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

Теперь это не так, поскольку сохраняются только требуемые записи (те, которые были вставлены) и разреженный массив типа intX_t (X в зависимости от размера словаря) <54529049344833> s полный сохраняется. Пустое пространство изменено с типа 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]: Новые реализации словарей работают лучше **в отношении памяти**, будучи более компактными; это главное преимущество здесь. Что касается скорости, то разница не столь существенна, есть места, где новый словарь может привести к небольшим регрессиям (поиск по ключу, например), в то время как в других (на ум приходят итерации и изменение размера) повышение производительности должно присутствовать. В целом, производительность словаря, особенно в реальных ситуациях, улучшается благодаря введенной компактности.
njzk2
11 октября 2016 в 19:19
18

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

Dimitris Fasarakis Hilliard
11 октября 2016 в 20:03
25

@ NJZK2 Когда предмет удален, соответствующий индекс заменен на DKIX_DUMMY со значением -2 и ввода в массиве заменены NULL , когда выполняется вставка, новые значения добавляются к массиву записей. Пока не удалось различить, но почти уверен, что когда индексы заполняются сверх порогового значения 2/3, выполняется изменение размера. Это может привести к уменьшению вместо увеличения, если существует много записей DUMMY.

Chris_Rands
13 марта 2017 в 22:29
0

Заметили ли вы какую-либо разницу в скорости с новой реализацией dict?

Dimitris Fasarakis Hilliard
14 марта 2017 в 13:26
3

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

Dimitris Fasarakis Hilliard
2 августа 2017 в 19:47
3

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

Chen A.
3 сентября 2017 в 18:13
0

@JimFasarakisHilliard, какие значения в списке indices? как 0, 1, 2 переводятся в реальный объект? это просто для ясности, или это фактическое значение в этом списке? Я думал, что он будет содержать значение hash ключа

Chris_Rands
9 апреля 2018 в 20:55
0

Есть какие-нибудь мысли о том, что произойдет с OrderedDict в будущем, я думаю, он будет сохранен для обратной совместимости? В настоящее время OrderedDict поддерживает итерацию reversed() и метод OrderedDict.move_to_end(), но, возможно, они будут добавлены и к обычному dict?

Dimitris Fasarakis Hilliard
10 апреля 2018 в 16:57
9

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

Dimitris Fasarakis Hilliard
10 апреля 2018 в 16:59
2

Соответствующее обсуждение SO можно найти здесь.

sinwoobang
29 августа 2018 в 06:01
3

@JimFasarakisHilliard Спасибо за подробный ответ. Я перевел его на корейский и распространил в группах Facebook Python Korea. blog.sinwoobang.me/post/176050610602/pythondictorder. Многие из Pythonista в Корее получили помощь от вашего поста. Еще раз спасибо.

ShadowRanger
19 июня 2019 в 01:34
0

@Chris_Rands: к вашему сведению, поддержка reversed появится в Python 3.8. Однако я не ожидаю увидеть move_to_end; компактный упорядоченный дизайн dict также не поддерживает это (придется оставить фиктивную запись и каждый раз находить и обновлять связанную запись индекса), в конечном итоге либо без необходимости расширяя массив записей, либо заставляя полный перефразировать явные манекены. Для сравнения, OrderedDict просто меняет пару указателей. Любой алгоритм, основанный на move_to_end, должен использовать OrderedDict; добавление поддержки к dict будет способствовать плохому коду.

tonix
13 октября 2019 в 09:33
0

Что означают -9092791511155847987, -8522787127447073495 и -6480567542315338377?

naught101
19 ноября 2019 в 00:04
0

Порядок создания все еще не гарантируется в 3.7? например a = {'one': 1, 'two': 2}?

Dimitris Fasarakis Hilliard
21 ноября 2019 в 07:48
0

@tonix это хэш-значения для ключей словаря.

ShadowRanger
20 февраля 2020 в 21:00
2

@ naught101: порядок создания гарантирован в версии 3.7; в вашем примере 'one' всегда будет повторяться до 'two' (где до версии 3.6 он менялся от запуска к запуску благодаря начальному значению хэша для каждого запуска, используемому для возмущения строковых хэшей для защиты от атак типа "отказ в обслуживании" с использованием ключей которые в противном случае можно было бы создать с идентичными хэшами).

matanster
4 октября 2020 в 09:04
0

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

Toilal
5 февраля 2021 в 15:09
1

Как насчет структуры данных set?

Pegasus
23 января 2022 в 15:22
0

collections.defaultdict тоже следит за порядком?

avatar
Peng
26 октября 2020 в 20:14
8

Чтобы полностью ответить на этот вопрос в 2020 году, позвольте мне процитировать несколько утверждений из официальных документов Python:

Изменено в версии 3.7: порядок словаря гарантированно соответствует порядку вставки. Это поведение было деталью реализации CPython из 3.6.

Изменено в версии 3.7: порядок словаря гарантированно соответствует порядку вставки.

Изменено в версии 3.8: Словари теперь обратимы.

Словари и представления словарей являются обратимыми.

Заявление относительно OrderedDict vs Dict:

Упорядоченные словари аналогичны обычным словарям, но имеют некоторые дополнительные возможности, связанные с операциями упорядочивания. Они стали менее важными теперь, когда встроенный класс dict получил возможность запоминать порядок вставки (это новое поведение стало гарантированным в Python 3.7).

avatar
rkengler
26 июля 2019 в 14:38
19

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

Python 3.8 включает функцию reversed() для словарей (удаление другого отличия от OrderedDict.

).

Dict и dictview теперь можно повторять в обратном порядке вставки с помощью reversed(). (Предоставлено Реми Лапейром в bpo-33462.) Посмотрите, что нового в Python 3.8

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

avatar
fjsj
15 декабря 2017 в 17:24
33

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

Jonny Waffles
14 июня 2018 в 18:54
3

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

fjsj
15 июня 2018 в 20:05
3

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

Chris_Rands
25 июня 2018 в 21:15
0

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

boatcoder
28 июня 2018 в 17:51
5

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

ZF007
3 июня 2019 в 00:18
5

Скорее всего, скоро появится «orderedDict» для людей, которым нравится возиться со своими воздуховодами из соображений безопасности ;p

avatar
Maresh
11 октября 2016 в 15:09
77

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

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

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

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

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

Сделайте свой код перспективным :)

Есть дебаты по этому поводу здесь.

EDIT: Python 3.7 сохранит это как функцию см.

xji
20 октября 2016 в 21:55
3

Кажется, что если они не имели в виду, что это будет реальная функция, а только деталь реализации, то тогда они даже не должны включать ее в документацию.

Chris_Rands
20 декабря 2017 в 09:23
4

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