ОБНОВЛЕНО на основе ответа Леннарта Регебро
Предположим, вы итерации через словарь, а иногда нужно удалить элемент. Следующие действия очень эффективны:
remove = []
for k, v in dict_.items():
if condition(k, v):
remove.append(k)
continue
# do other things you need to do in this loop
for k in remove:
del dict_[k]
Единственное накладное время здесь - создание списка ключей для удаления; если он не станет большим по сравнению со значением словаря, это не проблема. Однако этот подход требует некоторого дополнительного кодирования, поэтому он не очень популярен.
Популярный подход понимания речи:
dict_ = {k : v for k, v in dict_ if not condition(k, v)}
for k, v in dict_.items():
# do other things you need to do in this loop
приводит к полному копированию словаря и, следовательно, имеет риск появления глупых результатов, если словари растут большими, или вызывающая функция часто называется.
Гораздо лучший подход заключается в том, чтобы скопировать ключи, а не целые словарные слова:
for k in list(dict_.keys()):
if condition(k, dict_[k]):
del dict_[k]
continue
# do other things you need to do in this loop
(Обратите внимание, что все примеры кода находятся в Python 3, поэтому keys()
, items()
возвращает представление, а не копию.)
В большинстве случаев это не повредит производительности, так как время, чтобы проверить даже самое простое условие (не говоря уже о других вещах, которые вы делаете в цикле) обычно больше времени, чтобы добавить один ключ к список.
Тем не менее, мне интересно, можно ли даже избежать этого с помощью пользовательского словаря, который позволяет удалять во время итерации:
for k, v in dict_.items():
if condition(k, v):
del dict_[k]
continue
# do other things you need to do in this loop
Возможно, итератор всегда мог смотреть вперёд, так что, когда вызывается __next__
, итератор знает, куда идти, даже не глядя на текущий элемент (ему нужно было бы только взглянуть на элемент, когда он сначала добирается до него). И если нет следующего элемента, то итератор может просто установить флаг, который вызовет возникновение исключения StopIteration
всякий раз, когда __next__
вызывается снова.
Если элемент, который итератор пытается продвинуть, оказывается удаленным, он отлично подходит для создания исключения; нет необходимости поддерживать удаление при одновременном продолжении нескольких итераций.
Существуют ли какие-либо проблемы с этим подходом?
Одна из проблем заключается в том, что я не уверен, что это можно сделать без материальных накладных расходов по сравнению с существующими dict
; в противном случае было бы быстрее использовать подход list(dict_)
!
UPDATE:
Я пробовал все версии. Я не сообщаю о сроках, поскольку они явно зависят от конкретной ситуации. Но можно с уверенностью сказать, что во многих случаях самый быстрый подход, вероятно, будет list(dict_)
. В конце концов, если вы думаете, копия - это самая быстрая операция, которая линейно растет с размером списка; почти любые другие накладные расходы, если они также пропорциональны размеру списка, скорее всего, будут больше.
Мне очень нравятся все идеи, но поскольку я должен выбрать только один, я принимаю решение менеджера контекста, поскольку он позволяет использовать словарь как обычный или "улучшенный" с очень небольшими изменениями кода.