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

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

Например:

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

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

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

Ответ 1

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

Этот ответ не будет работать для 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

Как указано в ответе delnan, удаление записей вызывает проблемы, когда итератор пытается перейти к следующей записи. Вместо этого используйте метод 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}

Ответ 2

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

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

Мой любимый подход - это просто создать новый dict:

# 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)

Ответ 3

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

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

Ответ 4

Итерации вместо копии, например, возвращаемой items():

for k, v in list(mydict.items()):

Ответ 5

С 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 )
# {}

Я использую его, когда из словаря, использующего большую часть памяти, я хочу построить другой словарь (содержащий модификацию первого) без выполнения "копирования" и перегрузки ОЗУ.

Ответ 6

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

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

Ответ 7

list(mydict) чистый для использования 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.

Ответ 8

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

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]

Ответ 9

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

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])

Ответ 10

Существует способ, который может подойти, если элементы, которые вы хотите удалить, всегда находятся в "начале" итерации 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.