#python #list
Вопрос:
Если кто-нибудь попытается:
my_dict = {1: 1}
for key in my_dict:
my_dict.pop(key)
один получит:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
Python выдаст ошибку, так как вы изменили размер диктанта, просматривая его.
Как Python узнает, что это произошло, и может ли эта функция быть переопределена программно, чтобы код выполнялся?
И прежде чем кто-нибудь задаст неизбежный вопрос «почему я хотел бы это сделать»: я этого не делаю. Я задаю вопрос. Это называется любопытством.
например:
Допустим, у меня есть диктант с 5 пунктами. Приведенный выше код должен просто удалить все элементы в диктанте!
Комментарии:
1. Вы имеете в виду добавить a
5
для каждого из элементовmy_list
, присутствующих перед началом цикла?2. Нет, я хочу сделать это во время цикла. И да, я могу просто перебирать длину списка, а не его элементы, но я конкретно спрашиваю, могу ли я переопределить Python, чтобы не выдавать здесь ошибку.
3. Python в этом случае НЕ выдает ошибку. Это просто создает бесконечный цикл. Вы пробовали это сделать?
4. Первоначально словари Python не имели гарантированного порядка. Они повторялись в любом порядке, в каком бы ни находилась древовидная структура. Таким образом, добавление элемента может привести к перебалансировке дерева и искажению порядка, что сделает итератор бесполезным. Он больше не будет знать, где находится.
5. Вот соответствующий источник CPython: github.com/python/cpython/blob/…
Ответ №1:
Если вы поищете в исходном коде Python «словарь изменился в размере во время итерации», вы найдете Objects/dictobject.c
:
static PyObject*
dictiter_iternextkey(dictiterobject *di)
{
/* ... omitted ... */
if (di->di_used != d->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
"dictionary changed size during iteration");
di->di_used = -1; /* Make this state sticky */
return NULL;
}
ma_used
Поле-это просто количество элементов в словаре, как описано в dictobject.h
:
/* Number of items in the dictionary */
Py_ssize_t ma_used;
И di_used
является просто копией этого значения с момента создания итератора.
Вы не можете изменить это программно, по крайней мере, любым разумным способом (давайте не будем обезьянничать dict
). Вы можете создать свой собственный тип словаря, если хотите, и определить свой собственный итератор, который ведет себя по-другому.
Причина, по которой Python делает это, заключается в том, что трудно понять, что «правильно» делать, когда вы повторяете изменяющуюся хэш-таблицу.
Написание собственной реализации хэш-таблицы-хорошее упражнение, и вы быстро обнаружите проблему… когда вы вставляете или удаляете записи в хэш-таблице, это может изменить порядок других записей-допустимо ли, чтобы итератор пропускал записи или возвращал одну и ту же запись дважды? Скорее всего, нет. Можно ли создать структуру данных, которая обеспечивает требуемое поведение итерации? Да, но это сложно, и хэш-таблица, которая делает это, может работать хуже в других сценариях.
Комментарии:
1. Я бы скорее сказал, что причина, по которой Python делает это, заключается в том, что словари раньше были неупорядоченными. Со словарями, сохраняющими порядок вставки, поведение (теоретически) может быть четко определено, подобно изменению списка во время итерации.
2. @wim: Даже если результаты могут быть четко определены, реализация может быть неоправданно сложной.
Ответ №2:
Объект Python может возвращать свои собственные итераторы. Видишь https://wiki.python.org/moin/Iterator. Поэтому, когда __iter__()
вызывается объект dict, dict может установить флаг, по которому он повторяется. Этот же флаг будет снят, как только внутренний итератор израсходует все элементы в dict. Если в dict вызываются какие-либо изменения (например, с помощью pop
), функция проверяет флаг, чтобы узнать, можно ли внести изменения или если dict все еще находится в цикле итератора.