Eliminar múltiples claves de un diccionario de forma segura

127

Sé eliminar una entrada, 'clave' de mi diccionario d, de manera segura, usted hace:

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

Sin embargo, necesito eliminar varias entradas del diccionario de forma segura. Estaba pensando en definir las entradas en una tupla, ya que tendré que hacerlo más de una vez.

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

Sin embargo, me preguntaba si hay una manera más inteligente de hacer esto.

dublintech
fuente
3
El tiempo de recuperación de un diccionario es casi O (1) debido al hash. A menos que esté eliminando una proporción significativa de las entradas, no creo que lo haga mucho mejor.
ncmathsadist
1
La respuesta de @mattbornski parece más canónica y también sucinta.
Ioannis Filippidis
2
StackOverflow ha hablado: key in des más Pythonic que d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Si puede ahorrar un poco de memoria, puede hacerlo for x in set(d) & entities_to_remove: del d[x]. Esto probablemente solo será más eficiente si entities_to_removees "grande".
DylanYoung

Respuestas:

56

¿Por qué no así?

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]

Mattbornski proporcionó una versión más compacta con dict.pop ()

Glaslos
fuente
14
Agregando esto para las personas que vienen de un motor de búsqueda. Si se conocen las claves (cuando la seguridad no es un problema), se pueden eliminar varias claves en una línea como estadel dict['key1'], dict['key2'], dict['key3']
Tirtha R
Dependiendo de la cantidad de claves que esté eliminando, podría ser más eficiente usar for key in set(the_dict) & entries:y omitir la key in dictprueba.
DylanYoung
235
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
Mattbornski
fuente
37
Esta. Esta es la elección inteligente de Pythonista. dict.pop()elimina la necesidad de pruebas de existencia clave. Excelente.
Cecil Curry
44
Por lo que vale, creo que .pop()es malo y antipático, y preferiría la respuesta aceptada sobre esta.
Arne
55
Una cantidad asombrosa de personas parecen no molestarse por esto :) No me importa la línea adicional para verificar la existencia personalmente, y es significativamente más legible a menos que ya sepas sobre pop (). Por otro lado, si estaba tratando de hacer esto en una comprensión o lambda en línea, este truco podría ser de gran ayuda. También diría que es importante, en mi opinión, conocer gente donde están. No estoy seguro de que "malo y antipónico" vaya a dar a las personas que leen estas respuestas la orientación práctica que están buscando.
mattbornski
55
Hay una razón particularmente buena para usar esto. Si bien agregar una línea adicional puede mejorar la "legibilidad" o la "claridad", también agrega una búsqueda adicional al diccionario. Este método es el equivalente de eliminación de hacer setdefault. Si se implementa correctamente (y estoy seguro de que lo es), solo realiza una búsqueda en el mapa hash que es dict, en lugar de dos.
Físico loco
2
Personalmente, me preocuparía primero la corrección y la facilidad de mantenimiento, y la velocidad solo si se demuestra que no es lo suficientemente rápido. La diferencia de velocidad entre estas operaciones será trivial cuando se aleje al nivel de aplicación. Puede darse el caso de que uno sea más rápido, pero espero que en el uso en el mundo real no lo note ni le importe, y si lo nota y le importa, será mejor reescribirlo en algo más eficaz que Python.
mattbornski el
89

Usando comprensiones Dict

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

donde key1 y key2 son para ser eliminado.

En el ejemplo a continuación, las claves "b" y "c" se eliminarán y se mantendrán en una lista de claves.

>>> 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
fuente
44
nuevo diccionario? lista de comprensión? Debe ajustar la respuesta a la persona que hace la pregunta;)
Glaslos
66
Esta solución tiene un gran impacto en el rendimiento cuando la variable que lo contiene tiene un uso adicional en el programa. En otras palabras, un dict del que se han eliminado las claves es mucho más eficiente que un dict recién creado con los elementos retenidos.
Apalala
14
en aras de la legibilidad, sugiero {k: v para k, v en t.items () si k no está en [clave1, clave2]}
Frederic Bazin
8
Esto también tiene problemas de rendimiento cuando la lista de claves es demasiado grande, como toman las búsquedas O(n). Toda la operación es O(mn)dónde mestá el número de claves en el dict y nel número de claves en la lista. Sugiero usar un conjunto en su {key1, key2}lugar, si es posible.
ldavid
44
Para Apalala: ¿puedes ayudarme a entender por qué hay un impacto en el rendimiento?
Sean
21

una solución está usando mapy filterfunciones

pitón 2

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

pitón 3

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

usted obtiene:

{'c': 3}
José Ricardo Bustos M.
fuente
Esto no funciona para mí con 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))).... en python 3.4 la función de mapa devuelve un iterador
Jose Ricardo Bustos M.
44
o deque(map(...), maxlen=0)para evitar construir una lista de valores None; primera importación confrom collections import deque
Jason
19

Si también necesita recuperar los valores de las claves que está eliminando, esta sería una muy buena manera de hacerlo:

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

Por supuesto, aún podría hacer esto solo para eliminar las claves d, pero estaría creando innecesariamente la lista de valores con la comprensión de la lista. Tampoco es un poco claro usar una lista de comprensión solo para el efecto secundario de la función.

Andrew Clark
fuente
3
O si desea mantener las entradas eliminadas como un diccionario: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) y así sucesivamente.
poco el
Puede dejar de lado la asignación a una variable. De esta o de esa manera, es la solución más corta y más pitónica y debe marcarse como la respuesta principal en mi humilde opinión.
Gerhard Hagerer
12

Encontró una solución con popymap

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

El resultado de esto:

{'d': 'valueD'}

He respondido esta pregunta tan tarde solo porque creo que ayudará en el futuro si alguien busca lo mismo. Y esto podría ayudar.

Actualizar

El código anterior arrojará un error si no existe una clave en el 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)

salida:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
fuente
1
Esta respuesta arrojará una excepción si alguna clave keysno existe d; primero debe filtrarla.
ingofreyer
@ingofreyer actualizó el código para el manejo de excepciones. Gracias por encontrar este problema. Creo que ahora funcionará. :)
Shubham Srivastava
Gracias, esto debería ayudar a todos a encontrar esta respuesta :-)
ingofreyer
Crear una lista como un subproducto del uso del mapa, lo hace bastante lento, en realidad es mejor recorrerlo.
Charlie Clark
4

No tengo ningún problema con ninguna de las respuestas existentes, pero me sorprendió no encontrar esta solución:

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: me topé con esta pregunta desde aquí . Y mi respuesta está relacionada con esta respuesta .

Doug R.
fuente
3

Por qué no:

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

No sé a qué te refieres con "forma más inteligente". Seguramente hay otras formas, tal vez con la comprensión del diccionario:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viatán
fuente
2

en línea

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
fuente
2

Algunas pruebas de tiempo para cpython 3 muestran que un bucle simple es la forma más rápida y es bastante legible. Agregar una función tampoco causa mucha sobrecarga:

Resultados de timeit (10k iteraciones):

  • 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 iteraciones pequeñas, hacer eso 'en línea' fue un poco más rápido, debido a la sobrecarga de la llamada a la función. Pero del_alles sin pelusa, reutilizable y más rápido que todas las construcciones de mapeo y comprensión de Python.

Erik Aronesty
fuente
0

Creo que usar el hecho de que las claves se pueden tratar como un conjunto es la mejor manera si estás en 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))

Ejemplo:

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

Sería bueno tener un soporte completo para los métodos establecidos para los diccionarios (y no el lío impuro que estamos teniendo con Python 3.9) para que simplemente pueda "eliminar" un conjunto de claves. Sin embargo, mientras ese no sea el caso, y tenga un diccionario grande con una cantidad potencialmente grande de claves para eliminar, es posible que desee saber sobre el rendimiento. Entonces, he creado un código que crea algo lo suficientemente grande como para realizar comparaciones significativas: una matriz de 100,000 x 1000, por lo que 10,000,00 artículos en 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 millones de artículos o más no es inusual en algunos entornos. Comparando los dos métodos en mi máquina local, veo una ligera mejora al usarlo mapy pop, presumiblemente debido a menos llamadas de función, pero ambas toman alrededor de 2.5s en mi máquina. Pero esto palidece en comparación con el tiempo requerido para crear el diccionario en primer lugar (55s), o incluir verificaciones dentro del ciclo. Si esto es probable, es mejor crear un conjunto que sea una intersección de las teclas del diccionario y su filtro:

keys = cells.keys() & keys

En resumen: delya está muy optimizado, así que no se preocupe por usarlo.

Charlie Clark
fuente
-1

Llego tarde a esta discusión, pero para cualquier otra persona. Una solución puede ser crear una lista de claves como tal.

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

Luego use pop () en una lista de comprensión, o for loop, para iterar sobre las teclas y pop una a la vez como tal.

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

El 'n / a' es en caso de que la clave no exista, se debe devolver un valor predeterminado.

Terrance DeJesus
fuente
8
new_dictionaryse parece mucho a una lista;)
DylanYoung