Как удалить элементы из словаря, перебирая его?

295

Является ли законным удаление элементов из словаря в Python при его повторении?

Например:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

Идея состоит в том, чтобы удалить элементы, которые не удовлетворяют определенному условию, из словаря, вместо того, чтобы создавать новый словарь, который является подмножеством повторяемого итератора.

Это хорошее решение? Есть ли более элегантные / эффективные способы?

Trilarion
источник
1
Смежный вопрос с очень интересными ответами: stackoverflow.com/questions/9023078/… .
максимум
Можно было бы легко попробовать. Если это не удается, это не законно.
Триларион,
26
@ Триларион Можно было бы легко попробовать ... и легко узнать ничто ценное. Если это удается, это не обязательно законно. Крайних случаев и неожиданных предостережений предостаточно. Этот вопрос представляет нетривиальный интерес для всех потенциальных Pythonistas. Увольнение с размахиванием руки по приказу "Можно было легко попробовать!" бесполезен и противоречит пытливому духу запроса stackoverflow.
Сесил Карри
Ознакомившись макс «S связан вопрос , я должен согласиться. Вы, вероятно, просто хотите просмотреть этот беспокоящий углубленный вопрос и его хорошо написанные ответы. Ваш Pythonic разум будет взорван.
Сесил Карри
1
@CecilCurry Тестирование идеи перед тем, как представить ее здесь, является своего рода духом стекового потока, если я не ошибаюсь. Это было все, что я хотел передать. Извините, если из-за этого было какое-то беспокойство. Также я думаю, что это хороший вопрос, и я не отрицал его. Мне нравится ответ Йохена Ритцеля . Я не думаю, что нужно делать все эти вещи для удаления на лету, когда удаление на втором шаге намного проще. Это должно быть предпочтительным способом на мой взгляд.
Триларион

Ответы:

305

РЕДАКТИРОВАТЬ:

Этот ответ не будет работать для Python3 и даст RuntimeError.

RuntimeError: словарь изменил размер во время итерации.

Это происходит потому, что mydict.keys()возвращает итератор, а не список. Как указано в комментариях, просто преобразуйте mydict.keys()в список, list(mydict.keys())и это должно работать.


Простой тест в консоли показывает, что вы не можете изменять словарь во время итерации по нему:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Как указано в ответе Делнана, удаление записей вызывает проблемы, когда итератор пытается перейти к следующей записи. Вместо этого используйте keys()метод, чтобы получить список ключей и работать с ним:

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

Если вам нужно удалить на основе значения элементов, используйте items()вместо этого метод:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}
блэр
источник
53
Обратите внимание, что в Python 3 dict.items () возвращает итератор (а dict.iteritems () пропал).
Тим Лешер
83
Чтобы уточнить комментарий @TimLesher ... Это не будет работать в Python 3.
максимум
99
Чтобы уточнить разработку @ max, она будет работать, если вы преобразуете приведенный выше код с 2to3. Один из фиксаторов по умолчанию сделает цикл похожим, for k, v in list(mydict.items()):который отлично работает в Python 3. То же самое для keys()становления list(keys()).
Уолтер Мундт
8
Это не работает Я получаю сообщение об ошибке:RuntimeError: dictionary changed size during iteration
Томаш Зато - Восстановить Монику
15
@ TomášZato, как указал Уолтер, для python3 вам нужно использовать, так for k in list(mydict.keys()): как python3 делает метод keys () итератором, а также запрещает удаление элементов dict во время итерации. Добавляя вызов list (), вы превращаете итератор keys () в список. Поэтому, когда вы находитесь в теле цикла for, вы больше не выполняете итерацию по самому словарю.
Джефф Кромптон
89

Вы также можете сделать это в два этапа:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

Мой любимый подход, как правило, просто сделать новый диктат:

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)
Йохен Ритцель
источник
11
@senderle: с 2.7 на самом деле.
Йохен Ритцель
5
Подход к пониманию слова делает копию словаря; к счастью, значения по крайней мере не копируются, а просто связываются. Тем не менее, если у вас много ключей, это может быть плохо. По этой причине мне больше нравится removeциклический подход.
максимум
1
Вы также можете комбинировать действия:for k in [k for k in mydict if k == val]: del mydict[k]
AXO
первое решение является единственным эффективным на больших диктовках в этом потоке - поскольку оно не делает полную копию.
KXR
21

Вы не можете изменить коллекцию во время итерации. В этом и заключается безумие - особенно, если вам разрешат удалить и удалить текущий элемент, итератор должен будет двигаться дальше (+1), а следующий вызов, который nextприведет вас к этому (+2), так что вы в конечном итоге пропускаем один элемент (тот, что сразу за тем, который вы удалили). У вас есть два варианта:

  • Скопируйте все ключи (или значения, или оба, в зависимости от того, что вам нужно), затем итерируйте их. Вы можете использовать .keys()et al для этого (в Python 3 передать полученный итераторlist ). Может быть очень расточительно в отношении пространства, хотя.
  • Итерируйте mydictкак обычно, сохраняя ключи для удаления в отдельной коллекции to_delete. Когда вы закончите итерацию mydict, удалите все элементы to_deleteиз mydict. Сохраняет некоторое (в зависимости от того, сколько ключей удалено и сколько осталось) места по первому подходу, но также требуется еще несколько строк.

источник
You can't modify a collection while iterating it.это правильно для диктовок и друзей, но вы можете изменять списки во время итерации:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Нильс Линдеманн
3
@ Nils Это не выдает исключение, но это все еще неправильно. Обратите внимание: codepad.org/Yz7rjDVT - см., Например, stackoverflow.com/q/6260089/395760 для объяснения
Получил меня здесь. Тем не менее can'tэто верно только для dict и друзей, в то время как это должно быть shouldn'tдля списков.
Нильс Линдеманн
21

Вместо этого переберите копию, например, возвращенную items():

for k, v in list(mydict.items()):
Игнасио Васкес-Абрамс
источник
1
Это не имеет особого смысла - тогда вы не можете del vнапрямую, поэтому вы сделали копию каждого v, который вы никогда не собираетесь использовать, и вам все равно придется обращаться к элементам по ключу. dict.keys()это лучший выбор.
ОАО
2
@ Джош: Все зависит от того, сколько вы собираетесь использовать vв качестве критерия для удаления.
Игнасио Васкес-Абрамс
3
В Python 3 dict.items()возвращает итератор, а не копию. Смотрите комментарий для Блэра «S ответа , который ( к сожалению) также принимает на себя Python 2 семантику.
Сесил Карри
11

Это самый чистый для использования list(mydict):

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

Это соответствует параллельной структуре для списков:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

Оба работают в python2 и python3.

rsanden
источник
Это не хорошо, если ваш набор данных большой. Это копирование всех объектов в памяти, верно?
AFP_555
1
@ AFP_555 Да, моя цель - создать чистый, параллельный, pythonic код. Если вам нужна эффективность памяти, лучший из известных мне подходов состоит в том, чтобы выполнить итерацию и создать список ключей для удаления или новый набор элементов для сохранения. Красота - мой приоритет в Python; для больших наборов данных я использую Go или Rust.
rsanden
9

Вы можете использовать словарь понимания.

d = {k:d[k] for k in d if d[k] != val}

Аарон
источник
Это самый Pythonic.
Yehosef
Но он создает новый словарь вместо изменения dна месте.
Аристид
9

При использовании python3 итерирование в dic.keys () вызовет ошибку размера словаря. Вы можете использовать этот альтернативный способ:

Протестировано с python3, работает нормально, и ошибка " словарь изменился в размерах во время итерации " не возникает:

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}
glihm
источник
4

Вы можете сначала создать список ключей для удаления, а затем выполнить итерацию по этому списку, удалив их.

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]
Pob
источник
Это скорее копия первого решения @ Ritzel (эффективно для больших диктов без полной копии). Хотя "долго читать" без понимания списка. И все же, возможно, все же быстрее?
KXR
3

Существует способ, который может подойти, если элементы, которые вы хотите удалить, всегда находятся в «начале» итерации dict.

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

«Начало» гарантированно будет согласованным только для определенных версий / реализаций Python. Например, из Что нового в Python 3.7

природа сохранения порядка вставки объектов dict была объявлена ​​официальной частью спецификации языка Python.

Таким образом, вы избегаете копии указания, которое предлагают многие другие ответы, по крайней мере, в Python 3.

Михал Чарамза
источник
1

Я попробовал вышеупомянутые решения в Python3, но это, кажется, единственное, что работает для меня при хранении объектов в dict. По сути, вы делаете копию вашего dict () и перебираете его, удаляя записи в исходном словаре.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
JasonLandbridge
источник