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