Безопасное удаление нескольких ключей из словаря

129

Я знаю, что нужно безопасно удалить запись «ключ» из моего словаря d, вы:

if d.has_key('key'):
    del d['key']

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

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Однако мне было интересно, есть ли более умный способ сделать это?

dublintech
источник
3
Время извлечения из словаря составляет почти O (1) из-за хеширования. Если вы не удалите значительную часть записей, я не думаю, что вы добьетесь большего успеха.
ncmathsadist
1
Ответ @mattbornski кажется более каноничным и тоже лаконичным.
Иоаннис Филиппидис
2
StackOverflow сказал: key in dон более питонический, чем d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Майкл Шепер,
Если вы можете сэкономить немного памяти, вы можете это сделать for x in set(d) & entities_to_remove: del d[x]. Вероятно, это будет более эффективно, только если entities_to_removeбудет «большой».
DylanYoung,

Ответы:

57

Почему не так:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Более компактная версия была предоставлена ​​маттборнски с использованием dict.pop ()

Glaslos
источник
14
Добавляем это для людей, пришедших из поисковой системы. Если ключи известны (когда безопасность не является проблемой), несколько ключей можно удалить в одной строке, как этоdel dict['key1'], dict['key2'], dict['key3']
Tirtha R
В зависимости от количества ключей, которые вы удаляете, может быть более эффективно использовать for key in set(the_dict) & entries:и обойти key in dictтест.
DylanYoung,
237
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
mattbornski
источник
39
Это. Это выбор умного Pythonista. dict.pop()исключает необходимость проверки наличия ключей. Превосходно.
Сесил Карри,
4
Как бы то ни было, я считаю, что .pop()это плохо и непонятно, и предпочел бы принятый ответ этому.
Arne
5
Ошеломляющее количество людей, похоже, это не беспокоит :) Я лично не возражаю против дополнительной строки для проверки существования, и она значительно более читабельна, если вы еще не знаете о pop (). С другой стороны, если вы пытаетесь сделать это в понимании или встроенной лямбде, этот трюк может оказаться большим подспорьем. Еще скажу, что, на мой взгляд, важно знакомиться с людьми там, где они есть. Я не уверен, что «плохой и непифонический» даст людям, которые читают эти ответы, практическое руководство, которое они ищут.
mattbornski
5
Есть очень веская причина использовать это. Хотя добавление дополнительной строки может улучшить «читаемость» или «ясность», оно также добавляет дополнительный поиск в словарь. Этот метод является эквивалентом удаления setdefault. Если он реализован правильно (а я уверен, что это так), он выполняет только один поиск в хэш-карте dict, а не два.
Безумный физик
2
Лично меня в первую очередь заботит корректность и ремонтопригодность, а скорость - только в том случае, если будет доказано, что она недостаточно быстра. Разница в скорости между этими операциями будет тривиальной при увеличении масштаба до уровня приложения. Может случиться так, что один из них быстрее, но я ожидаю, что в реальном мире вы не заметите и не позаботитесь, а если вы заметите и позаботитесь, вам будет лучше переписывать что-то более производительное, чем Python.
mattbornski
90

Использование словосочетаний

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

где key1 и key2 должны быть удалены.

В приведенном ниже примере ключи «b» и «c» должны быть удалены, и он сохраняется в списке ключей.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 
Абхиджит Растоги
источник
4
новый словарь? понимание списка? Вы должны приспособить ответ к человеку,
задающему
6
У этого решения есть серьезный удар по производительности, когда переменная, содержащая, в дальнейшем используется в программе. Другими словами, dict, из которого были удалены ключи, намного эффективнее, чем вновь созданный dict с сохраненными элементами.
Apalala
15
для удобства чтения я предлагаю {k: v вместо k, v в t.items (), если k не в [key1, key2]}
Фредерик Базен
8
Это также имеет проблемы с производительностью, когда список ключей слишком велик, как происходит поиск O(n). Вся операция состоит в том O(mn), где m- количество ключей в слове и nколичество ключей в списке. Я предлагаю {key1, key2}вместо этого использовать набор , если это возможно.
ldavid
4
Апалале: не могли бы вы помочь мне понять, почему это так?
Шон
21

решение использует mapи filterфункции

питон 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

питон 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

ты получаешь:

{'c': 3}
Хосе Рикардо Бустос М.
источник
У меня это не работает с python 3.4:>>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Рисадинья
@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... в функции карты python 3.4 возвращает итератор
Хосе Рикардо Бустос М.
4
или deque(map(...), maxlen=0)чтобы не создавать список значений None; первый импорт сfrom collections import deque
Джейсон
19

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

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Конечно, вы все еще можете сделать это только для удаления ключей из d, но вы без необходимости создавали бы список значений с пониманием списка. Также немного непонятно использовать понимание списка только для побочного эффекта функции.

Эндрю Кларк
источник
3
Или, если вы хотите сохранить удаленные записи в виде словаря: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) и так далее.
kindall
Вы можете оставить присвоение переменной. Так или иначе, это самое короткое и самое питоническое решение, и его следует пометить как corect ответ IMHO.
Герхард Хагерер
12

Нашел решение с popиmap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

Результат этого:

{'d': 'valueD'}

Я ответил на этот вопрос так поздно только потому, что думаю, что в будущем это поможет, если кто-то будет искать то же самое. И это может помочь.

Обновить

Приведенный выше код выдаст ошибку, если ключ не существует в dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

вывод:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Шубхам Шривастава
источник
1
Этот ответ вызовет исключение, если какой-либо ключ keysне существует d- вам нужно сначала отфильтровать его.
ingofreyer
@ingofreyer обновил код для обработки исключений. Спасибо, что нашли эту проблему. Думаю, теперь заработает. :)
Шубхам Шривастава
Спасибо, это должно помочь каждому найти этот ответ :-)
ingofreyer
Создание списка в качестве побочного продукта использования карты делает это довольно медленным, на самом деле лучше перебирать его.
Чарли Кларк
4

У меня нет проблем ни с одним из существующих ответов, но я был удивлен, не найдя этого решения:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Примечание: я наткнулся на этот вопрос, исходящий отсюда . И мой ответ связан с этим ответом .

Дуг Р.
источник
3

Почему нет:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Я не знаю, что вы имеете в виду под «умнее». Конечно, есть и другие способы, возможно, с пониманием словаря:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
источник
2

в линию

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}
Чжуан Ван
источник
2

Некоторые временные тесты для cpython 3 показывают, что простой цикл for - самый быстрый способ, и он вполне читаем. Добавление функции также не вызывает особых накладных расходов:

Результаты timeit (10k итераций):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

Для небольших итераций выполнение этого «встроенного» было немного быстрее из-за накладных расходов на вызов функции. Но del_allон безопасен для ворса, его можно использовать повторно и быстрее, чем все конструкции понимания и сопоставления Python.

Эрик Аронести
источник
0

Я думаю, что использование того факта, что ключи можно рассматривать как набор, - лучший способ, если вы используете python 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Пример:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Реут Шарабани
источник
0

Было бы неплохо иметь полную поддержку методов набора для словарей (а не того нечестивого беспорядка, который мы получаем с Python 3.9), чтобы вы могли просто «удалить» набор ключей. Однако, если это не так, и у вас есть большой словарь с потенциально большим количеством ключей, которые нужно удалить, вы можете узнать о производительности. Итак, я создал код, который создает что-то достаточно большое для значимых сравнений: матрица 100 000 x 1000, итого 10 000 000 элементов.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 миллионов элементов и более - обычное дело для некоторых настроек. Сравнивая два метода на моем локальном компьютере, я вижу небольшое улучшение при использовании mapи pop, предположительно из-за меньшего количества вызовов функций, но оба метода на моем компьютере занимают около 2,5 с. Но это меркнет по сравнению со временем, необходимым для создания словаря в первую очередь (55 с) или включением проверок в цикле. Если это вероятно, лучше всего создать набор, который является пересечением ключей словаря и вашего фильтра:

keys = cells.keys() & keys

В итоге: delон уже сильно оптимизирован, поэтому не беспокойтесь об его использовании.

Чарли Кларк
источник
-1

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

k = ['a','b','c','d']

Затем используйте pop () в понимании списка или в цикле for, чтобы перебирать ключи и выскакивать по одному как таковые.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

«Н / п» означает, что если ключ не существует, необходимо вернуть значение по умолчанию.

Терренс ДеДжесус
источник
8
new_dictionaryужасно похоже на список;)
DylanYoung 05