Removendo várias chaves de um dicionário com segurança

127

Sei que para remover uma entrada, 'chave' do meu dicionário d, com segurança, você faz:

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

No entanto, preciso remover várias entradas do dicionário com segurança. Eu estava pensando em definir as entradas em uma tupla, pois precisarei fazer isso mais de uma vez.

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

No entanto, eu queria saber se existe uma maneira mais inteligente de fazer isso?

dublintech
fonte
3
O tempo de recuperação de um dicionário é quase O (1) devido ao hash. A menos que você esteja removendo uma proporção significativa das entradas, acho que você não fará muito melhor.
Ncmathsadist
1
A resposta de @mattbornski parece mais canônica e também sucinta.
Ioannis Filippidis
2
StackOverflow tem falado: key in dé mais do que Pythonic d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Se você pode poupar um pouco de memória, pode fazê-lo for x in set(d) & entities_to_remove: del d[x]. Provavelmente isso só será mais eficiente se entities_to_removefor "grande".
DylanYoung 15/04

Respostas:

56

Por que não assim:

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]

Uma versão mais compacta foi fornecida por mattbornski usando dict.pop ()

Glaslos
fonte
14
Adicionando isso a pessoas provenientes de um mecanismo de pesquisa. Se as chaves são conhecidas (quando a segurança não é um problema), várias chaves pode ser excluída em uma linha como estadel dict['key1'], dict['key2'], dict['key3']
Tirtha R
Dependendo do número de chaves que você está excluindo, pode ser mais eficiente usar for key in set(the_dict) & entries:e ignorar o key in dictteste.
DylanYoung
235
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
mattbornski
fonte
37
Este. Esta é a escolha inteligente do Pythonista. dict.pop()elimina a necessidade de testes de existência importantes. Excelente.
Cecil Curry
4
Pelo que vale, acho que .pop()é ruim e antitônico, e preferiria a resposta aceita a essa.
Arne
5
Um número impressionante de pessoas parece incomodado com isso :) Não me importo com a linha extra da existência verificando pessoalmente, e é significativamente mais legível, a menos que você já conheça pop (). Por outro lado, se você estava tentando fazer isso de forma compreensiva ou lambda embutida, esse truque poderia ser uma grande ajuda. Eu diria também que é importante, na minha opinião, conhecer pessoas onde elas estão. Não tenho certeza de que "ruim e não-tônico" ofereça às pessoas que estão lendo essas respostas a orientação prática que estão procurando.
mattbornski
5
Há uma razão particularmente boa para usar isso. A adição de uma linha extra pode melhorar a "legibilidade" ou a "clareza", mas também adiciona uma pesquisa extra ao dicionário. Este método é o equivalente a remoção de fazer setdefault. Se implementado corretamente (e tenho certeza de que é), ele faz apenas uma pesquisa no mapa de hash que é o dict, em vez de dois.
Mad Físico
2
Pessoalmente, eu me preocuparia primeiro com a correção e a manutenção, e a velocidade apenas se for comprovadamente insuficiente rápido. A diferença de velocidade entre essas operações será trivial quando o zoom for reduzido para o nível do aplicativo. Pode ser que seja mais rápido, mas espero que, no uso no mundo real, você não perceba nem se importe, e se perceber e se importar, será melhor reescrever algo mais eficiente que o Python.
mattbornski
89

Usando Compreensões de Dict

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

onde key1 e key2 devem ser removidos.

No exemplo abaixo, as teclas "b" e "c" devem ser removidas e mantidas em uma lista de chaves.

>>> 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}
>>> 
Abhijeet Rastogi
fonte
4
novo dicionário? compreensão da lista? Você deve ajustar a resposta para a pessoa fazer a pergunta;)
Glaslos
6
Essa solução tem um sério impacto no desempenho quando a variável que contém a variável tem mais uso no programa. Em outras palavras, um ditado do qual as chaves foram excluídas é muito mais eficiente do que um ditado recém-criado com os itens retidos.
Apalala 29/03
14
por uma questão de legibilidade, sugiro {k: v para k, v em t.items () se k não em [key1, key2]}
Frederic Bazin
8
Isso também apresenta problemas de desempenho quando a lista de chaves é muito grande, conforme as pesquisas O(n). Toda a operação é O(mn), onde mestá o número de chaves no ditado e no número de chaves na lista. Sugiro usar um conjunto {key1, key2}, se possível.
Ldavid
4
Para Apalala: você pode me ajudar a entender por que há um impacto no desempenho?
26416 Sean
21

uma solução está usando mape filterfunciona

python 2

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

python 3

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

você obtém:

{'c': 3}
Jose Ricardo Bustos M.
fonte
Isso não funciona para mim com o 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
@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... em python função 3.4 mapa retornar um iterador
José Ricardo Bustos M.
4
ou deque(map(...), maxlen=0)para evitar criar uma lista de valores Nenhum; primeira importação comfrom collections import deque
Jason
19

Se você também precisar recuperar os valores para as chaves que está removendo, seria uma boa maneira de fazê-lo:

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

Obviamente, você ainda pode fazer isso apenas para a remoção das chaves d, mas criaria desnecessariamente a lista de valores com a compreensão da lista. Também é um pouco obscuro usar uma compreensão de lista apenas para o efeito colateral da função.

Andrew Clark
fonte
3
Ou se você quiser manter as entradas excluídas como um dicionário: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) e assim por diante.
Kindall
Você pode deixar de fora a atribuição para uma variável. Dessa ou daquela maneira, é a solução mais curta e mais pitônica e deve ser marcada como resposta correta ao IMHO.
Gerhard Hagerer
12

Encontrou uma solução com popemap

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

A saída disso:

{'d': 'valueD'}

Eu respondi a essa pergunta tão tarde apenas porque acho que ajudará no futuro se alguém pesquisar a mesma. E isso pode ajudar.

Atualizar

O código acima gerará um erro se uma chave não existir no 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)

resultado:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
fonte
1
Esta resposta lançará uma exceção se nenhuma chave keysnão existir d- você precisaria filtrá-la primeiro.
ingofreyer
@ingofreyer atualizou o código para tratamento de exceções. Obrigado por encontrar este problema. Eu acho que agora vai funcionar. :)
Shubham Srivastava
Obrigado, isso deve ajudar a todos encontrar esta resposta :-)
ingofreyer
Criar uma lista como subproduto do uso do mapa, torna isso muito lento, é realmente melhor fazer um loop sobre ele.
Charlie Clark
4

Não tenho nenhum problema com nenhuma das respostas existentes, mas fiquei surpreso ao não encontrar esta solução:

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}

Nota: Eu me deparei com essa pergunta vindo daqui . E minha resposta está relacionada a essa resposta .

Doug R.
fonte
3

Por que não:

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

Não sei o que você quer dizer com "maneira mais inteligente". Certamente existem outras maneiras, talvez com compreensão de dicionário:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
fonte
2

na linha

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'}
chuang wang
fonte
2

Alguns testes de tempo para o cpython 3 mostram que um loop for simples é o caminho mais rápido e é bastante legível. A adição de uma função também não causa muita sobrecarga:

timeit resulta (10k iterações):

  • 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]

Para pequenas iterações, fazer isso 'inline' era um pouco mais rápido, devido à sobrecarga da chamada de função. Mas del_allé seguro para fiapos, reutilizável e mais rápido do que todas as construções de compreensão e mapeamento de python.

Erik Aronesty
fonte
0

Eu acho que usar o fato de que as chaves podem ser tratadas como um conjunto é a melhor maneira se você estiver no 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))

Exemplo:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Reut Sharabani
fonte
0

Seria bom ter suporte completo para métodos definidos para dicionários (e não para a bagunça profana que estamos enfrentando no Python 3.9), para que você possa simplesmente "remover" um conjunto de chaves. No entanto, desde que não seja esse o caso, e você tenha um dicionário grande com um número potencialmente grande de chaves para remover, convém saber sobre o desempenho. Então, eu criei um código que cria algo grande o suficiente para comparações significativas: uma matriz de 100.000 x 1000, portanto, 10.000,00 itens no total.

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 milhões de itens ou mais não é incomum em algumas configurações. Comparando os dois métodos na minha máquina local, vejo uma ligeira melhora ao usar mape pop, presumivelmente por causa de menos chamadas de função, mas ambas demoram cerca de 2,5 segundos na minha máquina. Mas isso empalidece em comparação com o tempo necessário para criar o dicionário (55s), ou incluindo verificações dentro do loop. Se isso for provável, é melhor criar um conjunto que seja uma interseção das chaves do dicionário e do seu filtro:

keys = cells.keys() & keys

Em resumo: deljá está fortemente otimizado, portanto, não se preocupe em usá-lo.

Charlie Clark
fonte
-1

Estou atrasado para esta discussão, mas para qualquer outra pessoa. Uma solução pode ser criar uma lista de chaves como tal.

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

Em seguida, use pop () em uma lista de compreensão, ou for loop, para iterar sobre as teclas e pop uma de cada vez, como tal.

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

O 'n / a' está no caso de a chave não existir, um valor padrão precisa ser retornado.

Terrance DeJesus
fonte
8
new_dictionaryparece muito com uma lista;)
DylanYoung