Чи впорядковані словники в Python 3.6+?


467

Словники упорядковані в Python 3.6 (принаймні під реалізацію CPython) на відміну від попередніх втілень. Це здається суттєвою зміною, але це лише короткий параграф у документації . Він описаний як деталі реалізації CPython, а не мовна функція, але також означає, що це може стати стандартним у майбутньому.

Як реалізація нового словника працює краще, ніж старша, зберігаючи порядок елементів?

Ось текст із документації:

dict()тепер використовує «компактне» представлення, запроваджене PyPy . Використання пам'яті нового dict () на 20–25% менше порівняно з Python 3.5. PEP 468 (Збереження порядку ** kwargs у функції.) Реалізовано цим. Аспект збереження порядку в цій новій реалізації вважається детальною інформацією про реалізацію, і на неї не слід покладатися (це може змінитися в майбутньому, але бажано мати нову реалізацію диктату мовою протягом декількох випусків, перш ніж змінювати специфікацію мови призначати збереження порядку для семантики для всіх поточних та майбутніх реалізацій Python; це також допомагає зберегти зворотну сумісність із старими версіями мови, де досі діє порядок випадкової ітерації, наприклад, Python 3.5). (За матеріалами INADA Naoki ввипуск 27350 . Ідея, яку спочатку запропонував Реймонд Хеттінгер .)

Оновлення грудня 2017 року: dictзбереження порядку вставки гарантується для Python 3.7


2
Дивіться цю тему в списку розсилки Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html, якщо ви її ще не бачили; це в основному дискусія навколо цих предметів.
mgc

1
Якщо зараз слід замовити kwargs (що є приємною ідеєю) і kwargs є dict, а не OrdersDict, то, мабуть, можна припустити, що ключі dict залишатимуться замовленими у майбутній версії Python, незважаючи на те, що в документації сказано інше.
Дмитро Синцов

4
@DmitriySintsov Ні, не робіть цього припущення. Це питання, порушене під час написання PEP, яке визначає особливості збереження порядку **kwargsі, як таке, формулювання використовується дипломатичним: **kwargsпідпис у функції тепер гарантується як відображення -збереження порядку вставки . Вони використовували термін відображення для того, щоб не змусити жодних інших реалізацій зробити упорядкований дікт (і використовувати OrderedDictвнутрішньо) і як спосіб сигналізувати, що це не повинно залежати від того, що dictне впорядковано.
Димитріс Фасаракіс Хілліард

7
Хороше відео пояснення від Реймонда Хеттінгера
Алекса

1
@wazoox, впорядкованість та складність хешмапу не змінилася. Зміна робить хешмап меншим, витрачаючи менше місця, а заощаджений простір (як правило?) Більше, ніж займає допоміжний масив. Швидше, менше, замовлене - ви можете забрати всі 3.
Джон Ла Руй

Відповіді:


510

Чи впорядковані словники в Python 3.6+?

Вони впорядковані [1] . Як і в Python 3.6, для реалізації CPython Python словники запам'ятовують порядок вставлених елементів . Це вважається деталізацією реалізації в Python 3.6 ; вам потрібно скористатися, OrderedDictякщо ви хочете впорядкувати вставку, що гарантується в інших реалізаціях Python (та інших впорядкованих поведінках [1] ).

Як і в Python 3.7 , це більше не є деталізацією реалізації, а натомість стає мовною особливістю. З повідомлення пітона-розробника GvR :

Зробіть так. "Дикт зберігає порядок вставки" є постановою. Дякую!

Це просто означає, що ви можете від цього залежати . Інші реалізації 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_t( Xзалежно від розміру диктату) 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]: Я кажу "впорядкована вставка", а не "впорядкована", оскільки, з існуванням OrdersDict, "впорядковано" передбачає подальшу поведінку, яку dictоб'єкт не передбачає . OrdersDicts є оборотними, забезпечують методи, що залежать від порядку, і, головним чином, забезпечують тести рівності для замовлень ( ==, !=). dictНа даний момент не пропонують жодної з цих форм поведінки / методів.


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

В цілому ефективність словника, особливо в реальних ситуаціях, покращується завдяки введеній компактності.


15
Отже, що відбувається, коли елемент вилучається? чи entriesзмінюється список? або порожнє місце зберігається? чи стискається час від часу?
njzk2

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

3
@Chris_Rands Ні, єдиний фактичний регрес, який я бачив, - це трекер у повідомленні Віктора . Окрім цього мікробензика, я не бачив жодного іншого питання / повідомлення, що вказувало б на серйозну різницю швидкостей у реальному навантаженні. Там є місця, де новий диктант може ввести незначні регресії (наприклад, пошук ключів), а в інших (ітерація та зміна розміру приходять на думку) буде присутнім підвищення продуктивності.
Димитріс Фасаракіс Хілліард

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

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

66

Нижче відповіді на початкове перше запитання:

Чи варто використовувати dictабо OrderedDictв Python 3.6?

Я думаю, що цього речення з документації насправді достатньо, щоб відповісти на ваше запитання

Аспект збереження порядку в цій новій реалізації розглядається як деталь реалізації, і на неї не слід покладатися

dictявно не означає, що це впорядкована колекція, тому, якщо ви хочете залишатися послідовними і не покладатися на побічний ефект нової реалізації, вам слід дотримуватися OrderedDict.

Зробіть свій майбутній доказ :)

Там є дебати про те, що тут .

EDIT: Python 3.7 збереже це як див


1
Здається, що якщо вони не означають, що це справжня особливість, а лише детальна інформація про реалізацію, тоді вони навіть не повинні вносити це в документацію.
xji

3
Я не впевнений у вашій редакції застережень; оскільки гарантія стосується лише Python 3.7, я вважаю, що поради щодо Python 3.6 не змінюються, тобто дикти вказуються в CPython, але на це не розраховуйте
Chris_Rands

25

Оновлення: Гідо ван Россум оголосив у списку розсилки, що станом на Python 3,7 dictс у всіх реалізаціях Python повинен зберігати порядок вставки.


2
Тепер, коли замовлення ключів є офіційним стандартом, яка мета OrdersDict? Або це тепер зайве?
Jonny Waffles

2
Я думаю OrderedDict НЕ буде зайвим , оскільки він має move_to_endметод і його рівність порядку чутливий: docs.python.org/3/library / ... . Дивіться примітку до відповіді Джима Фасаракіса Хілліарда.
fjsj

@JonnyWaffles см відповідь Джима і цей Q & A stackoverflow.com/questions/50872498 / ...
Chris_Rands

3
Якщо ви хочете, щоб ваш код працював однаково на 2.7 та 3.6 / 3.7 +, вам потрібно скористатися OrdersDict
boatcoder

3
Ймовірно, незабаром з'явиться "UnorderedDict" для людей, які люблять
обробляти

9

Я хотів додати до обговорення вище, але не маю репутації коментувати.

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

Dictview і dictviews тепер доступні для перегляду в зворотному порядку вставки, використовуючи reversed (). (Вкладає Ремі Лапейре в bpo-33462.) Подивіться, що нового в python 3.8

Я не бачу жодної згадки про оператор рівності чи інші особливості, OrderedDictтому вони все ще не зовсім однакові.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.