ОБНОВЛЕНО на основании ответа Леннарта Регебро
Предположим, что вы перебираете словарь, и иногда вам нужно удалить элемент. Следующее очень эффективно:
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_)
. В конце концов, если подумать, копирование - это самая быстрая операция, которая растет линейно с размером списка; почти любые другие издержки, если они также пропорциональны размеру списка, скорее всего, будут больше.
Мне действительно нравятся все идеи, но, поскольку мне нужно выбрать только одну, я принимаю решение для диспетчера контекста, поскольку оно позволяет использовать словарь как обычный или "расширенный" с очень небольшими изменениями кода.