Eliminar un elemento de un diccionario cuando se desconoce su clave

112

¿Cuál es la mejor manera de eliminar un elemento de un diccionario por valor, es decir, cuando se desconoce la clave del elemento? He aquí un enfoque simple:

for key, item in some_dict.items():
    if item is item_to_remove:
        del some_dict[key]

¿Hay mejores formas? ¿Hay algún problema con la mutación (eliminación de elementos) del diccionario mientras se itera?

Botones840
fuente
1
La razón subrayada para prohibir la mutación de dict mientras se itera es porque internamente hay un orden para la iteración, si mutas las claves, el orden se socavaría, lo que da como resultado un comportamiento desconocido.
Spectral

Respuestas:

92

Tenga en cuenta que actualmente está probando la identidad del objeto ( issolo devuelve Truesi ambos operandos están representados por el mismo objeto en la memoria; este no siempre es el caso con dos objetos que se comparan igual con ==). Si está haciendo esto a propósito, entonces podría reescribir su código como

some_dict = {key: value for key, value in some_dict.items() 
             if value is not value_to_remove}

Pero esto puede no hacer lo que quieres:

>>> some_dict = {1: "Hello", 2: "Goodbye", 3: "You say yes", 4: "I say no"}
>>> value_to_remove = "You say yes"
>>> some_dict = {key: value for key, value in some_dict.items() if value is not value_to_remove}
>>> some_dict
{1: 'Hello', 2: 'Goodbye', 3: 'You say yes', 4: 'I say no'}
>>> some_dict = {key: value for key, value in some_dict.items() if value != value_to_remove}
>>> some_dict
{1: 'Hello', 2: 'Goodbye', 4: 'I say no'}

Así que probablemente quieras en !=lugar de is not.

Tim Pietzcker
fuente
2
¿Eso es una compresión de diccionario? ¿Cuándo se agregaron?
Buttons840
4
puede usar some_dict.iteritems()aquí y poner fory ifdeclaraciones en líneas separadas para
facilitar la
3
Creo que se agregaron comprensiones de diccionario en Python 2.7.
mithrandi
2
@JF Sebastian: Estoy en Python 3 y iteritemsahora lo estoy items. En Python 2.7, de iteritems()hecho es mejor.
Tim Pietzcker
1
@ Buttons840 se denominan comprensiones de dictados en PEP 274 o pantallas de diccionario . como dice el PEP, se agregaron en 2.7 como hazañas 3.x retroportadas. alternativamente, puede alimentar dict()con una expresión generadora adecuada, que es 2.4. meta: puede navegar por los peps aquí para encontrar cosas.
n611x007
120

El dict.pop(key[, default])método le permite eliminar elementos cuando conoce la clave. Devuelve el valor en la clave si elimina el elemento; de lo contrario, devuelve lo que se pasa como default. Consulte los documentos .

Ejemplo:

>>> dic = {'a':1, 'b':2}
>>> dic
{'a': 1, 'b': 2}
>>> dic.pop('c', 0)
0
>>> dic.pop('a', 0)
1
>>> dic
{'b': 2}
N 1.1
fuente
4
OP preguntó sobre cuándo se desconoce la clave
nmz787
52
a = {'name': 'your_name','class': 4}
if 'name' in a: del a['name']
Kracekumar
fuente
OP preguntó sobre cuándo se desconoce la clave. Esta respuesta asume que se conoce la clave.
Jean-François Corbett
42

Una simple comparación entre del y pop () :

import timeit
code = """
results = {'A': 1, 'B': 2, 'C': 3}
del results['A']
del results['B']
"""
print timeit.timeit(code, number=100000)
code = """
results = {'A': 1, 'B': 2, 'C': 3}
results.pop('A')
results.pop('B')
"""
print timeit.timeit(code, number=100000)

resultado:

0.0329667857143
0.0451040902256

Entonces, del es más rápido que pop () .

Luu Tuan Anh
fuente
6
Sin embargo, la diferencia de rendimiento no es muy grande, y si desea evitar generar una excepción, puede proporcionar un segundo argumento pop()(como lo hace @ n-1-1 anteriormente), que no es una opción para el deloperador.
Alex Dupuy
1
Auxiliar a la pregunta, pero también había estado luchando por entender timeit. Gracias por este claro ejemplo.
Adam_G
OP preguntó sobre cuándo se desconoce la clave. Esta respuesta asume que se conoce la clave.
Jean-François Corbett
7

items()devuelve una lista, y es esa lista la que está iterando, por lo que mutar el dict en el ciclo no importa aquí. Si estuviera usando en su iteritems()lugar, mutar el dict en el bucle sería problemático , y también viewitems()en Python 2.7.

No puedo pensar en una mejor manera de eliminar elementos de un dictado por valor.

mithrandi
fuente
7

Construiría una lista de claves que deben eliminarse y luego las eliminaría. Es simple, eficiente y evita cualquier problema de iterar y mutar simultáneamente el dict.

keys_to_remove = [key for key, value in some_dict.iteritems()
                  if value == value_to_remove]
for key in keys_to_remove:
    del some_dict[key]

fuente
OP preguntó sobre cuándo se desconoce la clave. Esta respuesta asume que se conoce la clave.
Jean-François Corbett
1
y={'username':'admin','machine':['a','b','c']}
if 'c' in y['machine'] : del y['machine'][y['machine'].index('c')]
usuario3559640
fuente
0

No hay nada de malo en eliminar elementos del diccionario mientras se itera, como ha propuesto. Tenga cuidado con varios subprocesos que utilizan el mismo diccionario al mismo tiempo, lo que puede provocar un KeyError u otros problemas.

Por supuesto, consulte los documentos en http://docs.python.org/library/stdtypes.html#typesmapping

Himno de Thane
fuente
for k,v in d.iteritems(): del d[k]daría RuntimeError: dictionary changed size during iteration. Vea la explicación de mithrandi.
Buttons840
1
Por supuesto, d.iteritems () no es la forma en que el póster original está iterando, y no es a lo que me refería en mi respuesta.
Thane Anthem
0

Así es como lo haría yo.

for key in some_dict.keys():
    if some_dict[key] == item_to_remove:
        some_dict.pop(key)
        break
Nathan
fuente