Почему наборы Python не сохраняют порядок вставки?

12

Недавно я с удивлением обнаружил, что, хотя dicts гарантированно сохраняет порядок вставки в Python 3.7+, наборы не являются:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>> d['d'] = 4
>>> d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> s = {'a', 'b', 'c'}
>>> s
{'b', 'a', 'c'}
>>> s.add('d')
>>> s
{'d', 'b', 'a', 'c'}

В чем причина такой разницы? Разве те же улучшения эффективности, которые привели команду Python к изменению реализации dict, не применимы и к наборам?

Я не ищу указателей на реализации упорядоченных наборов или способов использования dicts в качестве замены для наборов. Мне просто интересно, почему команда Python не сделала встроенные наборы сохраняющими порядок в то же время, что и для диктовок.

Барт Робинсон
источник
1
Отвечает ли это на ваш вопрос? Есть ли у Python упорядоченный набор?
Михай Челару
1
Нет, я понимаю, что в Python нет встроенного упорядоченного набора. Мне просто интересно, почему это так, потому что теперь диктовки упорядочены.
Барт Робинсон
4
Шаблоны использования различны, поэтому они оптимизированы для разных вариантов использования. Это распространенное заблуждение, что в CPython наборы являются просто диктовками с нулевыми значениями, это совершенно неверно: реализации разные. Если ваш вопрос не будет закрыт, я могу опубликовать подробный ответ.
Вим
1
«Модели использования различны, поэтому они оптимизированы для разных вариантов использования». Я думаю, что хороший ответ на этот вопрос уточнил бы это. Вопрос в том, что делает два разных подхода оптимальными для соответствующих вариантов использования.
Карл Кнехтель
Обратите внимание, что PyPy использует одинаковый порядок для обоих dictи setначиная с 2.7.
Мистер Мияги

Ответы:

11

Наборы и дикты оптимизированы для разных вариантов использования. Основное использование набора - быстрое тестирование членства, которое не зависит от порядка. Для диктов стоимость поиска является наиболее важной операцией, и ключ, скорее всего, будет присутствовать. При использовании наборов наличие или отсутствие элемента заранее неизвестно, и поэтому реализация набора должна оптимизироваться как для найденного, так и для не найденного случая. Кроме того, некоторые оптимизации для общих операций над множествами, таких как объединение и пересечение, затрудняют сохранение порядка множеств без снижения производительности.

Хотя обе структуры данных основаны на хэше, распространенное заблуждение состоит в том, что наборы просто реализуются как диктовки с нулевыми значениями. Еще до реализации компактного dict в CPython 3.6 реализации set и dict уже значительно отличались, с небольшим повторным использованием кода. Например, в диктовках используется рандомизированное зондирование, но в наборах используется комбинация линейного зондирования и открытой адресации для улучшения локальности кэша. Первоначальный линейный зонд ( 9 шагов по умолчанию в CPython) проверит ряд смежных пар ключ / хэш, улучшая производительность за счет снижения затрат на обработку коллизий хэшей - последовательный доступ к памяти дешевле, чем разбросанных проб.

Теоретически было бы возможно изменить реализацию набора CPython, чтобы она была похожа на компактный, но на практике есть недостатки, и известные разработчики ядра были против такого изменения.

Наборы остаются неупорядоченными. (Почему? Шаблоны использования разные. Кроме того, разные реализации.)

- Гвидо ван Россум

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

- Раймонд Хеттингер

Подробное обсуждение вопроса о том, следует ли компактизировать наборы для 3.7, и ответы о том, почему было принято решение, можно найти в списках рассылки python-dev.

Таким образом, основные моменты заключаются в том, что шаблоны использования различны ( полезны правила размещения вставок, такие как ** kwargs , в меньшей степени - для наборов), экономия пространства для компактных наборов менее значительна (поскольку для уплотнения, в отличие от ключей, хэшей и значений), и вышеупомянутая линейная зондирование в наборах несовместима с компактной реализацией.

Я воспроизведу пост Рэймонда ниже, который охватывает наиболее важные моменты.

14 сентября 2016 года в 15:50 Эрик Сноу написал:

Затем я сделаю то же самое с сетами.

Если я не понял неправильно, Рэймонд был против внесения аналогичных изменений в настройках.

Вот так. Вот несколько мыслей на эту тему, прежде чем люди начнут нервничать.

  • Для компактного режима экономия пространства была чистой победой благодаря дополнительному пространству, занимаемому индексами, а перераспределение для массивов ключ / значение / хэш было более чем компенсировано улучшенной плотностью массивов ключ / значение / хэш. Однако для сетов сеть была гораздо менее благоприятной, потому что нам все еще нужны индексы и перераспределение, но мы можем только компенсировать стоимость пространства, уплотняя только два из трех массивов. Другими словами, сжатие имеет больше смысла, когда вы теряете место для ключей, значений и хэшей. Если вы потеряете один из этих трех, он перестает быть убедительным.

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

  • Я искал альтернативный путь для улучшения производительности набора. Вместо сжатия (что не было значительным выигрышем в космосе и привело к дополнительным издержкам), я добавил линейное зондирование, чтобы снизить стоимость коллизий и повысить производительность кэша. Это улучшение несовместимо с подходом к сжатию, который я отстаивал для словарей.

  • Пока побочный эффект упорядочения в словарях не гарантирован, поэтому преждевременно начинать настаивать на том, что множества также упорядочиваются. Документы уже ссылаются на рецепт для создания OrderedSet ( https://code.activestate.com/recipes/576694/ ), но похоже, что использование было почти нулевым. Кроме того, теперь, когда Эрик Сноу дал нам быстрый OrderedDict, проще, чем когда-либо, создать OrderedSet из MutableSet и OrderedDict, но, опять же, я не заметил особого интереса, потому что типичная аналитика данных типа «набор-набор» на самом деле не нужно или заботиться о заказе. Аналогично, основное использование быстрых проверок членства - независимость от порядка.

  • Тем не менее, я думаю, что есть место для добавления альтернативных реализаций множеств в PyPI. В частности, существуют некоторые интересные особые случаи для данных, которые можно заказать, когда операции set-to-set могут быть ускорены путем сравнения целых диапазонов ключей (см. Https://code.activestate.com/recipes/230113-implementation-of- устанавливает-используя-отсортированные списки для начальной точки). Во IIRC, PyPI уже есть код для фильтров типа Блума и хеширования кукушки.

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

- Раймонд Хеттингер

С [Python-Dev] Python 3.6 dict становится компактным и получает приватную версию; и ключевые слова становятся упорядоченными , сентябрь 2016.

Wim
источник
Спасибо! Я принял этот ответ, потому что он наиболее прямо отвечает на первоначальный вопрос. Ответ Pylang ниже также содержит некоторые полезные ссылки на более поздние обсуждения между разработчиками Python.
Барт Робинсон
3

Обсуждение

Ваш вопрос уместен и уже интенсивно обсуждался на python-devs . Р. Хеттингер поделился списком обоснований в этой теме . Состояние вопроса теперь остается открытым, вскоре после подробного ответа Т. Петерса.

Короче говоря, реализация современных диктов, которая сохраняет порядок вставки, уникальна и не считается подходящей для множеств. В частности, dicts используются везде для запуска Python (например,__dict__ в пространствах имен объектов). Основным мотивом современного диктата было уменьшение размера, что делает Python более эффективным в плане памяти. Напротив, наборы менее распространены, чем диктанты в ядре Python, и, таким образом, отговаривают такой рефакторинг. См. Также доклад Р. Хеттингера о современной реализации диктов.


перспективы

Неупорядоченная природа множеств в Python аналогична поведению математических множеств . Заказ не гарантируется.

Соответствующее математическое понятие неупорядочено, и было бы странно навязывать такой порядок - Р. Хеттингер

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

Наличие неупорядоченных множеств согласуется с очень общей и вездесущей структурой данных, которая раскручивает самую современную математику, т.е. теорию множеств . Я утверждаю, что неупорядоченные наборы в Python - это хорошо иметь.

Смотрите также похожие посты, которые раскрывают эту тему:

pylang
источник