Deep Engineering
Средний·Опубликовано·3.11 · 3.12 · 3.13 · 3.14·30 МИН

Встроенные коллекции: list, tuple, dict и set — что когда брать

Вопрос «что выбрать — список, кортеж, словарь или множество» звучит как вопрос вкуса, а решается тремя проверками: нужен ли порядок, нужны ли повторы и как вы будете искать. Прогон отвечает числами: проверка x in по списку из тысячи элементов медленнее, чем по множеству, в 216 раз, а на десяти — всего в 3,4 (CPython 3.13.7, одна машина); словарь хранит порядок вставки, а множество строк печатается по-разному при разных PYTHONHASHSEED; и множество на тысячу чисел в 4,1 раза больше списка.

Полное техническое изложение

TL;DR

В Python четыре основные встроенные коллекции, и выбирают между ними по трём вопросам: нужен ли порядок, нужны ли повторы и как вы будете искать. Список — упорядоченный и изменяемый, с повторами. Кортеж — то же, но неизменяемый. Словарь — ключи и значения, ключи уникальны. Множество — уникальные элементы без порядка.

Отсюда главное следствие: если проверок «есть ли такой элемент» много, делать их стоит по множеству или словарю, а не по списку. Список проверяет элементы по очереди, множество находит элемент по хешу — числу, которое объект вычисляет из своего значения (hash(x)). В среднем это O(1): время почти не зависит от размера; в худшем случае, когда хеши совпадают, — O(n) (подробно — в уроке «Сложность операций»). В нашем прогоне (CPython 3.13.7, одна машина) на тысяче элементов x in по списку было медленнее, чем по множеству, в 216 раз, на ста тысячах — в тринадцать тысяч; на своей машине отношение надо мерить. Плата за это — память и хешируемость: множество на тысячу чисел в 4,1 раза больше списка, а элементы множества и ключи словаря должны быть хешируемыми — список ими быть не может. Значения словаря — могут: {"items": []} работает.

Дальше — то, что отличает понимание от пересказа. На десяти элементах разница всего в 3,4 раза, а первый элемент список находит так же быстро, как множество. Строить множество из списка ради одной проверки в замере не окупилось — это дольше одного перебора. Словарь с Python 3.7 хранит порядок вставки — это гарантия языка, а множество порядка не обещает: множество из пяти строк при трёх разных PYTHONHASHSEED напечаталось в трёх разных порядках. А очередь на списке — ошибка: на ста тысячах элементов list.pop(0) в 299 раз дороже deque.popleft().

Порог входа
Перед уроком достаточно понимать
  • как создать список и словарь и пройти по ним циклом for;
  • что такое изменяемый и неизменяемый объект;
  • что выражение x in y проверяет вхождение;
  • что такое класс и экземпляр — урок «Классы и ООП»: он объясняет, почему экземпляры обычных классов хешируемы;
  • запись O(1) и O(n) — хотя бы как «не растёт» и «растёт вместе с размером»; подробно — в уроке «Сложность операций».
Заранее знать не нужно
  • хешируемость, frozenset, dict.fromkeys;
  • порядок словаря и множества, PYTHONHASHSEED;
  • цена x in, list.pop(0) и collections.deque.

База: четыре коллекции и три вопроса

коллекцияпорядокповторыизменяемаякак ищут
listдададапо индексу; по значению — перебором
tupleдаданетпо индексу; по значению — перебором
dictпорядок вставкиключи уникальныдапо ключу, через хеш
setнетнетдапо значению, через хеш

Ответ уровня Junior на «что когда брать»: список — для упорядоченного набора, который меняется; кортеж — для неизменяемой записи из нескольких полей; словарь — когда значение ищут по ключу; множество — когда нужны уникальные элементы и быстрая проверка «есть ли такой». Это верно, и за этим ответом обычно идут три уточнения: насколько быстрее проверка по множеству, почему список нельзя положить в множество и что с порядком. Им и посвящены механизмы ниже.

Чем список отличается от кортежа в памяти и по скорости — отдельный урок, «Список против кортежа». Здесь — выбор между всеми четырьмя.

Механизм 1: повторы и уникальность

контракт языкаУникальность элементов множества и ключей словаря — модель данных и документация словарей; вывод — прогон bench/collections/basics.py, раздел 1, одинаковый на CPython 3.11–3.14.

Вывод bench/collections/basics.py, раздел 1:

1) дубли
   list(data)          -> [3, 1, 3, 2, 1]
   set(data)           -> {1, 2, 3}
   dict.fromkeys(data) -> {3: None, 1: None, 2: None}
   {'a': 1, 'a': 2}    -> {'a': 2}
   len(set(data)) == len(data) -> False

Список хранит всё, что в него положили. Множество — только разные элементы, и порядок исходного списка теряется. Словарь хранит уникальные ключи, и при повторе ключа побеждает последнее значение.

Из этого вывода складываются два рабочих приёма:

  • «Есть ли дубли» — len(set(data)) != len(data).
  • «Убрать дубли, сохранив порядок» — list(dict.fromkeys(data)). Через set так нельзя: у множества порядка нет (Механизм 3), а у словаря он есть.

Механизм 2: хешируемость — кто может быть ключом

контракт языкаТребование хешируемости к ключам словаря и элементам множества — документация словарей и модель данных; вывод — прогоны bench/collections/hashable.py и bench/collections/basics.py, раздел 2, на CPython 3.11–3.14. Текст ошибки на 3.14 изменился — см. историю версий.

Элементы множества и ключи словаря должны быть хешируемыми. К значениям словаря это требование не относится. Множество и словарь находят элемент или ключ по хешу, поэтому хеш у него должен быть и не должен меняться; значение словаря лежит рядом с ключом, по нему не ищут, и хешировать его незачем:

Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.

перевод

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

— Встроенные типы — словари

Это видно на полном коротком примере. Полный файл bench/collections/hashable.py:

PYTHON
import sys
 
print("PY", sys.version.split()[0])
 
d = {"items": []}
d["items"].append(1)
print('{"items": []} after append:', d)
print('{(1, 2): "ok"}:', {(1, 2): "ok"})
 
for text, make in [('{[]: "x"}', lambda: {[]: "x"}), ("{[1, 2]}", lambda: {[1, 2]})]:
    try:
        make()
    except TypeError as e:
        print(text, "-> TypeError:", e)

Запуск — python3.13 bench/collections/hashable.py; вывод на CPython 3.13.7 (на 3.11 и 3.12 — тот же):

PY 3.13.7
{"items": []} after append: {'items': [1]}
{(1, 2): "ok"}: {(1, 2): 'ok'}
{[]: "x"} -> TypeError: unhashable type: 'list'
{[1, 2]} -> TypeError: unhashable type: 'list'

Словарь со списком в значении создался и даже изменился на месте; кортеж стал ключом. Список не годится ни в ключ, ни в элемент множества. Подробнее о том, что хешируемо, — вывод bench/collections/basics.py, раздел 2:

2) хешируемость: что можно класть в set и в ключи dict
   {[1, 2]}            -> TypeError: unhashable type: 'list'
   {[1, 2]: 'x'}       -> TypeError: unhashable type: 'list'
   {(1, 2)}            -> исключения нет
   {(1, [2])}          -> TypeError: unhashable type: 'list'
   {frozenset({1, 2})} -> исключения нет
   {{1, 2}}            -> TypeError: unhashable type: 'set'

Строка, которую стоит запомнить, — {(1, [2])}. Кортеж хешируем, только если хешируемо всё, что в нём лежит: хеш кортежа считается из хешей элементов, а у списка его нет. Поэтому ключом «из нескольких полей» служит кортеж неизменяемых значений, а множество множеств строят из frozenset.

Почему хеша нет у изменяемых объектов, которые сравниваются по значению, — списков, словарей, множеств: словарь кладёт ключ по хешу и потом ищет его по тому же хешу. Изменись такой ключ после вставки — его хеш изменился бы, и словарь искал бы не там. Со значением такой беды нет: его не ищут по хешу, поэтому d["items"].append(1) выше ничего не ломает. Экземпляры обычных классов изменяемы, но хешируемы: они сравниваются по тождеству, а не по значению, — об этом урок «Классы и ООП». Как это устроено внутри, разобрано в статье о словарях.

Механизм 3: порядок — у словаря есть, у множества нет

контракт языкаПорядок вставки словаря — гарантия языка с 3.7; отсутствие порядка у множества — модель данных. Вывод — прогон bench/collections/basics.py, раздел 3; порядки при одинаковом PYTHONHASHSEED совпали на CPython 3.11–3.14.

Dictionaries preserve insertion order. Note that updating a key does not affect the order. Keys added after deletion are inserted at the end.

перевод

Словари хранят порядок вставки. Обновление значения по ключу порядок не меняет. Ключи, добавленные после удаления, вставляются в конец.

— Встроенные типы — словари

У множества такой гарантии нет. Порядок, в котором оно печатается, зависит от хешей элементов, а хеш строки от запуска к запуску разный:

By default, the __hash__() values of str and bytes objects are "salted" with an unpredictable random value. Although they remain constant within an individual Python process, they are not predictable between repeated invocations of Python.

перевод

По умолчанию значения __hash__() у объектов str и bytes „подсаливаются“ непредсказуемым случайным значением. В пределах одного процесса Python они постоянны, но между повторными запусками Python непредсказуемы.

— Модель данных — __hash__

Это случайное «зерно» можно зафиксировать переменной окружения PYTHONHASHSEED. Скрипт запустил сам себя трижды с разными значениями — вывод bench/collections/basics.py, раздел 3:

3) порядок
   dict в порядке вставки: ['дыня', 'яблоко', 'груша']
   удалили и вставили снова — ключ в конце: ['дыня', 'груша', 'яблоко']
   множество строк при разных PYTHONHASHSEED:
   PYTHONHASHSEED=1: ['дыня', 'груша', 'слива', 'яблоко', 'вишня']
   PYTHONHASHSEED=2: ['вишня', 'яблоко', 'дыня', 'слива', 'груша']
   PYTHONHASHSEED=3: ['дыня', 'слива', 'яблоко', 'груша', 'вишня']
   разных порядков из трёх запусков: 3
   множество целых {5, 1, 3}: {1, 3, 5} — порядок от хеша, а не от вставки
   hash(1), hash(3), hash(5): 1 3 5 — хеш небольшого целого равен ему самому
   множество целых {8, 1}: {8, 1} — и не всегда по возрастанию

Последние строки — ловушка наоборот. Множество маленьких целых часто печатается «по возрастанию», потому что хеш небольшого целого равен ему самому. Но это совпадение реализации, а не сортировка: {8, 1} напечаталось как {8, 1}, а код, который опирается на порядок, сломается на строках. Если нужен порядок — список, словарь или sorted(...).

Механизм 4: цена поиска — где разница огромна, а где её нет

наблюдение замераbench/collections/cost.py, раздел 1: медианы девяти кругов вперемежку, CPython 3.13.7, одна машина (2 ядра); 3.14.7 — в записи cost-3.14.txt, отношения того же порядка. Отношения — наблюдение этой конфигурации: они зависят от размера и вида данных, версии и машины; у себя их надо мерить. Цена построения множества — bench/collections/build.py, 14.10.2026, linux/x86_64.

Документация называет «быструю проверку вхождения» первым применением множеств. Насколько быструю — замер. Искомый элемент последний, то есть для списка худший случай:

размерlistsetdict (ключи)list / set
10119,5 нс35,2 нс35,4 нс3,4
1 0009 690,0 нс44,9 нс48,4 нс216,0
100 0001 052 432,4 нс79,4 нс56,7 нс13 250,6

Список проверяет элементы по очереди, поэтому его время растёт вместе с размером. Множество и словарь вычисляют хеш и идут в нужное место таблицы; когда хеши распределены хорошо, как у этих целых, проверить приходится немного ячеек, — поэтому их время растёт в разы, а не в тысячи раз, как у списка. Кортеж ведёт себя как список: это тоже последовательность.

Но O(1) у множества и словаря — средняя оценка, а не обещание одной проверки. Если у многих ключей один и тот же хеш, поиск сравнивает их по очереди, и худший случай — O(n): в уроке «Сложность операций» поиск в множестве из ста объектов с одинаковым хешем вызвал __eq__ 105 раз, а не один. И одна проверка сама не бесплатна: она стоит вычисления хеша и вызова __eq__, а у длинной строки и то и другое проходит по её символам.

В числах есть и сам цикл for, в котором шла проверка: пустой цикл стоит 18,7 нс на шаг — строка «пустой цикл» в записи прогона. На больших размерах это мелочь, а на десяти элементах — половина времени множества, поэтому 3,4 раза — оценка снизу: сама проверка отличается сильнее.

Но правило «всегда множество» на маленьких коллекциях не работает. На десяти элементах разница всего в 3,4 раза, а если искомый элемент первый, список находит его за 33,7 нс — так же быстро, как множество. И строить множество ради одной проверки невыгодно: построение хеширует каждый элемент и раскладывает его по таблице. Замер выше этого не видит — там коллекции готовы до начала отсчёта, — поэтому построение мерили отдельно. Вывод bench/collections/build.py (CPython 3.13.7):

построить set ради проверок: нс на сценарий, медианы 9 кругов
    размер  проверок   по списку   set(lst) + проверки
        10         1        98.8                 211.1
        10       100      8499.6                1962.7
      1000         1      4638.7                7926.6
      1000       100    596511.4               10674.0

Ради одной проверки построение множества проиграло перебору и на десяти, и на тысяче элементов; ради ста — выиграло. Множество окупается, когда проверок много; если же оно уже есть, оно быстрее и на десяти элементах. Где точка окупаемости на ваших данных — вопрос замера, а не правила.

Механизм 5: очередь — не список

наблюдение замераbench/collections/cost.py, раздел 2: 50 000 удалений из коллекции в 100 000 элементов, создание коллекции в замер не входит; медианы девяти кругов, CPython 3.13.7.

Вывод bench/collections/cost.py, раздел 2:

2) удаление из начала: 50 000 удалений из коллекции в 100 000, нс на удаление
   (создание коллекции в замер не входит)
   форма                            мин   медиана      макс
   list.pop(0)                  11920.9   12662.3   13358.6
   deque.popleft()                 41.6      42.4      47.3
   list.pop() с конца              42.5      43.7      46.3
   list.pop(0) / deque.popleft() по медианам: 299

Список хранит элементы подряд, и удаление первого сдвигает все остальные на одну позицию — чем длиннее список, тем дороже. При удалении с конца сдвигать нечего, поэтому list.pop() дешёвый. Для очереди «добавляем в конец, забираем из начала» в стандартной библиотеке есть collections.deque — a list-like sequence optimized for data accesses near its endpoints (похожая на список последовательность, оптимизированная для доступа к данным у её концов). В замере popleft() дешевле list.pop(0) в 299 раз.

Глубже: что стоит память

наблюдение замераbench/collections/basics.py, разделы 4 и 6: sys.getsizeof самого контейнера на 1000 ключей, CPython 3.13.7; на 3.14.7 кортеж на 8 байт больше, раздел 6 одинаков на 3.11–3.14. Устройство записей — заголовки CPython 3.13.7 и 3.14.7. Размер — свойство реализации CPython, а не гарантия языка.

Вывод bench/collections/basics.py, раздел 4:

4) память: сам контейнер на 1000 целых, байт (sys.getsizeof)
   list     8056
   tuple    8040
   set     32984
   dict    36952
   set / list: 4.1

sys.getsizeof считает сам контейнер, без элементов: числа общие и в счёт не идут. Список и кортеж хранят по указателю на элемент — 8 байт на 64-битной сборке, плюс заголовок. Множество и словарь держат хеш-таблицу с запасом пустых ячеек — без запаса поиск по хешу перестал бы быть быстрым, — плюс служебные данные. Отсюда в 4,1 раза больше памяти у множества: это цена быстрого in. Но 4,1 — отношение для тысячи целых на этой версии, а не константа: оно зависит от числа и вида элементов и от реализации.

Устроены множество и словарь в CPython по-разному. У множества каждая ячейка таблицы — указатель на элемент и его запомненный хеш (setentry, Include/cpython/setobject.h:20–23 в CPython 3.13.7). Словарь — это таблица индексов (dk_indices) плюс отдельный массив записей (dk_entries). Запись общего вида хранит хеш, ключ и значение (PyDictKeyEntry, Include/internal/pycore_dict.h:76–81), а запись словаря, у которого все ключи — строки, только ключ и значение (PyDictUnicodeEntry, строки 83–86): хеш строка хранит в самой себе (поле hash, Include/cpython/unicodeobject.h:101). Это видно по памяти — вывод bench/collections/basics.py, раздел 6, одинаковый на 3.11–3.14:

6) память и вид ключей: 1000 ключей, байт (sys.getsizeof)
   dict, ключи int     36952
   dict, ключи str     26032
   set, int            32984
   set, str            32984

Словарь на тех же тысяче ключах, но строковых, меньше на 10 920 байт; у множества размер от вида элементов не зависит. Детали — в статьях об устройстве списка и словаря.

Как отвечать на собеседовании

Короткий ответ: список — упорядоченный изменяемый набор с повторами, кортеж — неизменяемый, словарь — ключ → значение с уникальными ключами, множество — уникальные элементы без порядка. Проверку вхождения и поиск по значению делают по множеству или словарю: там это поиск по хешу — O(1) в среднем и O(n) в худшем случае, когда хеши совпадают, — а в списке — перебор. Элементом множества и ключом словаря может быть только хешируемый объект; значения словаря могут быть любыми. Словарь хранит порядок вставки, множество — нет.

Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.

Если интервьюер копает глубже

На «насколько быстрее множество». «Зависит от размера и от данных. В моём прогоне на CPython 3.13.7 x in по списку на тысяче элементов был медленнее в 216 раз, на десяти — в 3,4, а первый элемент список находит так же быстро. Строить множество ради одной проверки не стоит: в замере это дольше одного перебора, окупилось на ста проверках. O(1) у множества — среднее: при совпадающих хешах поиск вырождается в перебор. Точные отношения на другой машине — мерить».

На «почему список нельзя положить в множество». «Множество ищет по хешу, а хеш изменяемого объекта мог бы поменяться после вставки. Поэтому у списка хеша нет. Кортеж хешируем, только если хешируемо всё, что в нём: {(1, [2])} падает. А значением словаря список быть может — по значениям не ищут: {"items": []} работает».

На «упорядочен ли словарь». «С 3.7 порядок вставки — гарантия языка. Удалённый и вставленный снова ключ уходит в конец. У множества порядка нет: множество строк при разных PYTHONHASHSEED печаталось по-разному».

На «как сделать очередь». «collections.deque. list.pop(0) сдвигает все элементы — у меня он был в 299 раз дороже popleft() на ста тысячах».

Дальше спросят

Спросят дальше

Как посчитать, сколько раз встречается каждый элемент?

Короткий ответ

Словарём: counts[x] = counts.get(x, 0) + 1. Или готовым collections.Counter(data) — это словарь, у которого отсутствующий ключ даёт ноль, плюс методы вроде most_common(). Для группировки похожая заготовка — collections.defaultdict(list).

Спросят дальше

Что такое frozenset и зачем он?

Короткий ответ

Неизменяемое множество. Раз оно не меняется, у него есть хеш — и его можно положить в другое множество или сделать ключом словаря. Обычное множество для этого не годится: {{1, 2}} падает с unhashable type: 'set'.

Спросят дальше

Чем словарь отличается от списка пар?

Короткий ответ

Поиском. В списке пар [(ключ, значение), …] найти значение по ключу — это перебор, как x in list. Словарь находит ключ по хешу — в среднем за время, почти не зависящее от размера, — и гарантирует уникальность ключей. Список пар уместен, когда ключи повторяются или нехешируемы.

Частые заблуждения

Утверждение

Проверку вхождения всегда надо делать по множеству

На самом деле

Если множество уже есть и проверок много — да. Но на десяти элементах разница всего в 3,4 раза, а первый элемент список находит за 33,7 нс, как множество. Строить множество ради одной проверки — это хешировать каждый элемент: в замере на CPython 3.13.7 это вышло дольше одного перебора и на десяти, и на тысяче элементов.

Утверждение

Всё, что лежит в словаре, должно быть хешируемым

На самом деле

Только ключи — и элементы множества. Значение может быть списком или другим словарём: {"items": []} создаётся и меняется на месте, а {[]: "x"} падает с unhashable type: 'list'.

Утверждение

Поиск по множеству — всегда одна проверка

На самом деле

O(1) — средняя оценка при хорошо распределённых хешах. Если хеши совпадают, множество сравнивает элементы по очереди: в уроке «Сложность операций» поиск среди ста объектов с одним хешем вызвал __eq__ 105 раз. Худший случай — O(n).

Утверждение

Множество сортирует элементы

На самом деле

Множество маленьких целых часто, но не всегда печатается по возрастанию — хеш такого числа равен ему самому; {8, 1} напечаталось как {8, 1}. Множество строк при разных PYTHONHASHSEED напечаталось в трёх разных порядках.

Утверждение

Словарь не хранит порядок

На самом деле

С Python 3.7 хранит порядок вставки — это гарантия языка, а не деталь реализации. Удалённый и вставленный снова ключ уходит в конец.

Утверждение

Кортеж всегда можно сделать ключом словаря

На самом деле

Только если хешируемо всё, что в нём лежит. {(1, [2])} падает с unhashable type: 'list'.

Утверждение

Очередь удобно делать на списке через pop(0)

На самом деле

Удаление первого элемента сдвигает все остальные: на ста тысячах list.pop(0) оказался в 299 раз дороже deque.popleft().

История версий

ВерсияИзменениеЧто это значит для кода
3.7Порядок вставки словаря становится гарантией языка. В CPython 3.6 это была деталь реализации, полагаться на которую было нельзя, а раньше порядка не было вовсе.
3.11Опорная точка урока. Наблюдения скрипта на 3.11, 3.12 и 3.13 совпадают построчно, включая порядки множества при одинаковом PYTHONHASHSEED. Записи словаря со строковыми ключами без поля под хеш (PyDictUnicodeEntry) есть во всех четырёх проверенных версиях.
3.14Текст ошибки о нехешируемом объекте называет роль: cannot use 'list' as a set element (unhashable type: 'list') и cannot use 'list' as a dict key (…); для {(1, [2])} — cannot use 'tuple' as a set element (unhashable type: 'list'). Кортеж на 1000 элементов стал на 8 байт больше — 8048 против 8040. Поведение коллекций не изменилось.
3.15Новый встроенный неизменяемый тип frozendict (PEP 814). Урок его не запускал: Python 3.15 вышел 9 октября 2026, наши прогоны — 3.11–3.14. По документу What’s New In Python 3.15 (сверено 14.10.2026): A frozendict is hashable as long as all of its keys and values are hashable (frozendict хешируем, если хешируемы все его ключи и значения) — в отличие от обычного словаря, где хешируемыми обязаны быть только ключи.

Практика

Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.

Практика · что напечатает

Из списка слов убирают дубли двумя способами и считают длины. Что напечатает этот код?
words = ["b", "a", "b", "c", "a"]
print(list(dict.fromkeys(words)))
print(sorted(set(words)))
print(len(set(words)), len(words))

Практика · оцените

Список и множество из тысячи чисел; проверяется 999 in — последний элемент. Во сколько раз проверка по списку медленнее?
раз

Проверьте себя

Вопрос 1 из 5

Нужно хранить уникальные id пользователей и часто проверять, есть ли id в наборе. Что подходит лучше всего?

Чем измерено

Числа этого урока получены этими скриптами. Каждый открывается прямо отсюда — вместе с записью прогона: на чём считали, что получилось и с каким разбросом.

Наблюдения без времени — CPython 3.11.15, 3.12.3, 3.13.7 и 3.14.7; замеры времени — 3.13.7 и 3.14.7 на одной машине с двумя ядрами. Скрипты bench/collections/hashable.py, bench/collections/build.py и раздел 6 bench/collections/basics.py добавлены и запущены 14.10.2026 на linux/x86_64. Контракты и цитаты сверены 14.10.2026. Python 3.15 (вышел 9 октября 2026) урок не запускал — о нём только строка в истории версий.

Источники и что читать дальше

6 ИСТОЧНИКОВ

  1. Модель данных — множестваОфициальная документация. Что такое множество и зачем оно: «These represent unordered, finite sets of unique, immutable objects» (Это неупорядоченные конечные множества уникальных неизменяемых объектов). И прямо названные применения: «Common uses for sets are fast membership testing, removing duplicates from a sequence, and computing mathematical operations such as intersection, union, difference, and symmetric difference» (Обычные применения множеств — быстрая проверка вхождения, удаление дублей из последовательности и математические операции: пересечение, объединение, разность и симметрическая разность). Текст сверен по встроенной справке pydoc (модуль pydoc_data.topics) CPython 3.13.7 и 3.14.7.https://docs.python.org/3.13/reference/datamodel.html#the-standard-type-hierarchy
  2. Встроенные типы — словариОфициальная документация. Гарантия порядка: «Dictionaries preserve insertion order. Note that updating a key does not affect the order. Keys added after deletion are inserted at the end» (Словари хранят порядок вставки. Обновление значения по ключу порядок не меняет. Ключи, добавленные после удаления, вставляются в конец). И с какой версии: «Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6» (Изменено в версии 3.7: порядок словаря гарантированно совпадает с порядком вставки. Начиная с CPython 3.6 это было деталью реализации). И какие ключи допустимы: «Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys» (Нехешируемые значения — то есть содержащие списки, словари или другие изменяемые типы, которые сравниваются по значению, а не по тождеству, — ключами быть не могут). Сверено по pydoc_data.topics 3.13.7 и 3.14.7.https://docs.python.org/3.13/library/stdtypes.html#mapping-types-dict
  3. Модель данных — __hash__Официальная документация. Почему порядок множества строк меняется от запуска к запуску: «By default, the __hash__() values of str and bytes objects are “salted” with an unpredictable random value. Although they remain constant within an individual Python process, they are not predictable between repeated invocations of Python» (По умолчанию значения __hash__() у объектов str и bytes „подсаливаются“ непредсказуемым случайным значением. В пределах одного процесса Python они постоянны, но между повторными запусками Python непредсказуемы). И что это значит для множеств: «Changing hash values affects the iteration order of sets. Python has never made guarantees about this ordering» (Изменение значений хеша влияет на порядок обхода множеств. Python никогда не давал гарантий относительно этого порядка). Сверено по pydoc_data.topics 3.13.7 и 3.14.7.https://docs.python.org/3.13/reference/datamodel.html#object.__hash__
  4. collections.dequeОфициальная документация. Строка документации типа в интерпретаторе: «A list-like sequence optimized for data accesses near its endpoints» (Похожая на список последовательность, оптимизированная для доступа к данным у её концов). Сверено вызовом collections.deque.__doc__ на 3.13.7 и 3.14.7.https://docs.python.org/3.13/library/collections.html#collections.deque
  5. Include/internal/pycore_dict.h и Include/cpython/setobject.hИсходный код CPython. Как устроены записи таблиц в CPython 3.13.7 (сверено по заголовкам, поставляемым с интерпретатором, 14.10.2026). Запись словаря общего вида — pycore_dict.h:76–81, PyDictKeyEntry: «/* Cached hash code of me_key. */» (запомненный хеш ключа) и поля me_hash, me_key, me_value. Запись словаря со строковыми ключами — строки 83–86, PyDictUnicodeEntry: только me_key и me_value, «/* The key must be Unicode and have hash. */» (ключ обязан быть строкой и иметь хеш). Сама хеш-таблица словаря — массив индексов dk_indices, строки 167–180: «Actual hash table of dk_size entries. It holds indices in dk_entries» (Собственно хеш-таблица из dk_size ячеек. Она хранит индексы в dk_entries). Запись множества — setobject.h:20–23, setentry: key и hash, «/* Cached hash code of the key */» (запомненный хеш ключа). На 3.14.7 те же структуры — pycore_dict.h:80–90; PyDictUnicodeEntry есть и в заголовках 3.11 и 3.12.https://github.com/python/cpython/blob/v3.13.7/Include/internal/pycore_dict.h
  6. What’s New In Python 3.15 — frozendictОфициальная документация. PEP 814: «A new immutable type, frozendict, is added to the builtins module. … A frozendict is hashable as long as all of its keys and values are hashable» (В модуль builtins добавлен новый неизменяемый тип frozendict. … frozendict хешируем, если хешируемы все его ключи и значения). Python 3.15 вышел 9 октября 2026; урок его не запускал — цитата сверена 14.10.2026.https://docs.python.org/3.15/whatsnew/3.15.html