Python не дает никаких обещаний о том, когда (если вообще когда-либо) закончится этот l oop. Изменение набора во время итерации может привести к пропущенным элементам, повторным элементам и другим странностям. Никогда не полагайтесь на такое поведение.
Все, что я собираюсь сказать, это детали реализации, которые могут быть изменены без предварительного уведомления. Если вы пишете программу, которая опирается на какую-либо из них, ваша программа может нарушить любую комбинацию Python реализации и версии, отличную от CPython 3.8.2.
Краткое объяснение причин l oop оканчивается на 16 в том, что 16 - это первый элемент, который оказался с более низким индексом таблицы ha sh, чем предыдущий элемент. Полное объяснение приведено ниже.
Внутренняя таблица ha sh из набора Python всегда имеет степень 2 размера. Для таблицы размером 2 ^ n, если нет столкновений, элементы сохраняются в позиции в таблице ha sh, соответствующей n младших значащих битов их ha sh. Вы можете увидеть это реализованным в set_add_entry
:
mask = so->mask;
i = (size_t)hash & mask;
entry = &so->table[i];
if (entry->key == NULL)
goto found_unused;
Самые маленькие Python целых га sh для себя; в частности, все целые в вашем тесте имеют sh для себя. Это можно увидеть в long_hash
. Поскольку ваш набор никогда не содержит двух элементов с одинаковыми младшими битами в своих хэшах, столкновения не происходит.
A Python Итератор набора отслеживает свою позицию в наборе с простым целочисленным индексом в наборе внутренний ха sh стол. Когда запрашивается следующий элемент, итератор ищет заполненную запись в таблице ha sh, начиная с этого индекса, затем устанавливает свой сохраненный индекс сразу после найденной записи и возвращает элемент записи. Вы можете увидеть это в setiter_iternext
:
while (i <= mask && (entry[i].key == NULL || entry[i].key == dummy))
i++;
si->si_pos = i+1;
if (i > mask)
goto fail;
si->len--;
key = entry[i].key;
Py_INCREF(key);
return key;
Ваш набор изначально начинается с таблицы ha sh размера 8 и указателя на 0
int объект с индексом 0 в таблице ha sh. Итератор также размещается по индексу 0. При итерации элементы добавляются в таблицу ha sh, каждый при следующем индексе, потому что именно там их ha sh говорит поместить их, и это всегда следующий индекс итератора. смотрит на. Удаленные элементы имеют фиктивный маркер, сохраненный в их старом положении, в целях разрешения столкновений. Вы можете видеть, что реализовано в set_discard_entry
:
entry = set_lookkey(so, key, hash);
if (entry == NULL)
return -1;
if (entry->key == NULL)
return DISCARD_NOTFOUND;
old_key = entry->key;
entry->key = dummy;
entry->hash = -1;
so->used--;
Py_DECREF(old_key);
return DISCARD_FOUND;
Когда в набор добавлено 4
, количество элементов и макетов в наборе становится достаточно большим, чтобы set_add_entry
вызывает перестроение таблицы ha sh, вызывая set_table_resize
:
if ((size_t)so->fill*5 < mask*3)
return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
so->used
- это число заполненных, не фиктивных записей в таблице ha sh, которое равно 2, поэтому set_table_resize
получает 8 в качестве второго аргумента. Исходя из этого, set_table_resize
решает новый размер таблицы ha sh должен быть 16:
/* Find the smallest table size > minused. */
/* XXX speed-up with intrinsics */
size_t newsize = PySet_MINSIZE;
while (newsize <= (size_t)minused) {
newsize <<= 1; // The largest possible value is PY_SSIZE_T_MAX + 1.
}
Он перестраивает таблицу ha sh с размером 16. Все элементы все еще заканчиваются на своих старых индексах в новой таблице ha sh, так как в их хешах не было установлено никаких старших битов.
По мере продолжения l oop элементы продолжают размещаться на следующей Индекс будет смотреть итератор. Запущено еще одно перестроение таблицы ha sh, но новый размер по-прежнему равен 16.
Шаблон прерывается, когда l oop добавляет 16 в качестве элемента. Нет индекса 16 для размещения нового элемента в. 4 младших бита из 16 равны 0000, что означает 16 с индексом 0. В этот момент сохраненный индекс итератора равен 16, и когда l oop запрашивает следующий элемент у итератора, итератор видит, что он прошел мимо конец таблицы ha sh.
Итератор завершает l oop в этот момент, оставляя только 16
в наборе.