Eliminar un elemento de un diccionario

1396

¿Hay alguna forma de eliminar un elemento de un diccionario en Python?

Además, ¿cómo puedo eliminar un elemento de un diccionario para devolver una copia (es decir, sin modificar el original)?

richzilla
fuente
13
¿Por qué necesita una función que devuelve un diccionario, cuando puede modificar el diccionario directamente?
amillerrhodes
55
El diccionario popmétodo cambia el diccionario en el lugar . Por lo tanto, altera la referencia al diccionario que se pasó de la persona que llama a la "función auxiliar". Por lo tanto, la "función auxiliar" no necesita devolver nada, ya que la referencia original al diccionario en la persona que llama ya estará alterada. No asigne el retorno de dict.pop()a nada si no lo necesita. EG: do stuff with my_dict; my_dict.pop(my_key, None); do more stuff with my_dict # now doesn't have my_key. Úselo deepcopy(my_dict)si es necesario.
Mark Mikofski
1
Como el título original no estaba de acuerdo con los detalles y excluía específicamente la solución obvia d.pop(), arreglé el título para hacer la pregunta especificada en los detalles.
smci
1
Deberíamos agregar una advertencia preguntándole si realmente desea hacer esto, ya que si lo hace N veces en un diccionario con elementos E, perderá (/ usará) memoria O (N * E) con todas las copias profundas. Si simplemente quiere una lectura de solo lectura (copia superficial), hágalo d.pop(key). Pero si algo modifica alguna vez la copia superficial, tiene un problema bien conocido con alias . Ayuda si nos cuenta el contexto más amplio. (¿Algo más está modificando los valores dict? ¿Estás tratando de iterar destructivamente sobre una lista? Si no, ¿qué?)
smci
55
"¿Por qué necesita una función que devuelve un diccionario, cuando puede modificar el diccionario directamente?" ¿Quizás porque quieres escribir funciones puras que no modifiquen sus parámetros?
Gene Callahan

Respuestas:

1729

La deldeclaración elimina un elemento:

del d[key]

Sin embargo, esto muta el diccionario existente, por lo que el contenido del diccionario cambia para cualquier otra persona que tenga una referencia a la misma instancia. Para devolver un nuevo diccionario, haga una copia del diccionario:

def removekey(d, key):
    r = dict(d)
    del r[key]
    return r

El dict()constructor hace una copia superficial . Para hacer una copia profunda, vea el copymódulo .


Tenga en cuenta que hacer una copia para cada dict del/ asignación / etc. significa que vas del tiempo constante al tiempo lineal, y también estás usando el espacio lineal. Para pequeños dictados, esto no es un problema. Pero si planea hacer muchas copias de grandes dictados, probablemente desee una estructura de datos diferente, como un HAMT (como se describe en esta respuesta ).

Greg Hewgill
fuente
15
ese es un gran punto sobre la mutabilidad de los diccionarios +1, aunque no puedo pensar en un momento en que quisiera copias del diccionario, siempre he confiado en que la copia 'todos' sea la misma. gran punto
tMC
30
@tMC Si edita el archivo dictmientras lo recorre, le dará un error:RuntimeError: dictionary changed size during iteration
VertigoRay
15
¿Qué pasa con el popmétodo que de hecho hace lo mismo? ¿No es más pitónico? (siendo el método dict, no una palabra reservada especial)?
Serge
21
Esta respuesta tiene una debilidad, podría ser engañosa. Los lectores pueden malinterpretar que dict (d) puede darles una copia con 'd'. Pero es una copia incompleta. Cuando solo se realizan operaciones de del keys, está bien. Pero cuando quiere hacer algo más a un dict anidado, modificar 'r' usando ese método de copia puede causar un cambio al 'd' original. Para obtener una copia auténtica, primero necesita 'importar copia' y luego 'r = copy.deepcopy (d)'.
Zen
10
@Zen: Bastante justo, he agregado una nota sobre copia superficial vs. profunda.
Greg Hewgill
264

pop Muta el diccionario.

 >>> lol = {"hello": "gdbye"}
 >>> lol.pop("hello")
     'gdbye'
 >>> lol
     {}

Si desea conservar el original, simplemente puede copiarlo.

Cristal
fuente
29
"del" está bien, pero "pop" parece más "Pythonic", en mi opinión.
ivanleoncz
1
@ivanleoncz ¿por qué?
kevr
3
popdevuelve el valor que fue 'reventado', lo que le permite usar este valor por cualquier otra razón. Si no es más "Pythonic", diría que parece mejor, seguro :). No es un dict, pero funciona de la misma manera para ambos: github.com/ivanlmj/python-prototypes/blob/master/3.4/…
ivanleoncz
2
@ivanleoncz También es mejor por una razón más, popse le puede proporcionar un valor predeterminado que se devolverá cuando falta una clave en dict. Es bueno cuando necesita eliminar algunas claves, pero algunas pueden faltar; deltiraría KeyErroren tal caso.
itachi
80

Creo que su solución es la mejor manera de hacerlo. Pero si desea otra solución, puede crear un nuevo diccionario utilizando las claves del diccionario anterior sin incluir la clave especificada, como esta:

>>> a
{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> {i:a[i] for i in a if i!=0}
{1: 'one', 2: 'two', 3: 'three'}
utdemir
fuente
2
Realmente genial. Me gusta el método rápido para filtrar un diccionario sin definir una nueva función.
Joe J
66
Para aquellos que no están familiarizados con las comprensiones, también pueden hacer algo como esto: {i:a[i] for i in a if i not in [0, 1, 2]}si desean eliminar varios elementos.
kmatheny
99
Mejor sería {k:v for k,v in a.items() if k != 0}, creo.
rlbond
1
La mejor solución para eliminar un elemento por clave y devolver el resultado del nuevo dict en la misma línea. Por ejemplo, si necesita usar un dict ya construido sin un solo elemento como **kwargs,some_function(**{k:v for k,v in some_dict.items() if k not 'some_key'})
Cole
La mejor solución aquí. Un revestimiento y no muta el diccionario original.
Andrew Winterbotham
55

La declaración del es lo que estás buscando. Si tiene un diccionario llamado foo con una clave llamada 'bar', puede eliminar 'bar' de foo de esta manera:

del foo['bar']

Tenga en cuenta que esto modifica permanentemente el diccionario que se está utilizando. Si desea conservar el diccionario original, deberá crear una copia de antemano:

>>> foo = {'bar': 'baz'}
>>> fu = dict(foo)
>>> del foo['bar']
>>> print foo
{}
>>> print fu
{'bar': 'baz'}

La dictllamada hace una copia superficial. Si quieres una copia profunda, úsala copy.deepcopy.

Aquí hay un método que puede copiar y pegar, para su conveniencia:

def minus_key(key, dictionary):
    shallow_copy = dict(dictionary)
    del shallow_copy[key]
    return shallow_copy
arussell84
fuente
1
@ pythonian29033, en realidad, no . La respuesta aceptada funciona como se esperaba: devuelve el dict sin una clave. El enfoque de esta respuesta muta dict original;) ​​Hay una diferencia significativa
maxkoryukov
1
@ arussell84, ¿por qué >>>se usa a menudo en los ejemplos de python? Sí, python-doc contiene muchas de esas cosas. Pero dicho código no es conveniente para copiar . Estoy confundido ...
maxkoryukov
@maxkoryukov sí lo hace! pero esa función y esta respuesta son exactamente las mismas, con la excepción de que esa respuesta está dentro de una función. y no debe haber estado codificando en Python por un tiempo, los >>>imita la notación de escucha de pitón en el modo CLI
pythonian29033
2
@ pythonian29033 sobre >>>. Sí, es de estilo REPL, pero hablemos con franqueza: el único hombre había escrito esta muestra, y 1000 lo han leído. Creo que sería genial escribir ejemplos de la manera que permite copiar y ejecutar fácilmente. No me gusta eliminar estos corchetes angulares a mano. O copie línea por línea ... Así que no entiendo: ¿por qué estos ángulos siguen ahí))) ¿Puede ser que no sé algo?
maxkoryukov
3
He agregado una función que se puede copiar / pegar, para su conveniencia.
arussell84
48

Hay muchas respuestas agradables, pero quiero enfatizar una cosa.

Puede usar tanto el dict.pop()método como una deldeclaración más genérica para eliminar elementos de un diccionario. Ambos mutan el diccionario original, por lo que debe hacer una copia (consulte los detalles a continuación).

Y ambos aparecerán KeyErrorsi la clave que les está proporcionando no está presente en el diccionario:

key_to_remove = "c"
d = {"a": 1, "b": 2}
del d[key_to_remove]  # Raises `KeyError: 'c'`

y

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove)  # Raises `KeyError: 'c'`

Tienes que ocuparte de esto:

capturando la excepción:

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    del d[key_to_remove]
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

y

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    d.pop(key_to_remove)
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

realizando una verificación:

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    del d[key_to_remove]

y

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    d.pop(key_to_remove)

pero pop()también hay una forma mucho más concisa: proporcione el valor de retorno predeterminado:

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove, None)  # No `KeyError` here

A menos que utilice pop()para obtener el valor de una clave que se eliminará, puede proporcionar cualquier cosa, no necesaria None. Aunque podría ser que usarlo delcon incheck es un poco más rápido debido a que pop()es una función con sus propias complicaciones que causan sobrecarga. Por lo general, no es el caso, por lo que pop()con el valor predeterminado es lo suficientemente bueno.


En cuanto a la pregunta principal, tendrá que hacer una copia de su diccionario, guardar el diccionario original y tener uno nuevo sin quitar la clave.

Algunas otras personas aquí sugieren hacer una copia completa (profunda) con copy.deepcopy(), que podría ser una exageración, una copia "normal" (superficial), usando copy.copy()o dict.copy(), podría ser suficiente. El diccionario mantiene una referencia al objeto como valor para una clave. Entonces, cuando elimina una clave de un diccionario, se elimina esta referencia, no el objeto al que se hace referencia. El objeto en sí puede ser eliminado más tarde automáticamente por el recolector de basura, si no hay otras referencias para él en la memoria. Hacer una copia profunda requiere más cálculos en comparación con una copia superficial, por lo que disminuye el rendimiento del código al hacer la copia, desperdiciando memoria y proporcionando más trabajo al GC, a veces una copia superficial es suficiente.

Sin embargo, si tiene objetos mutables como valores de diccionario y planea modificarlos más adelante en el diccionario devuelto sin la clave, debe hacer una copia profunda.

Con copia superficial:

def get_dict_wo_key(dictionary, key):
    """Returns a **shallow** copy of the dictionary without a key."""
    _dict = dictionary.copy()
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}

Con copia profunda:

from copy import deepcopy


def get_dict_wo_key(dictionary, key):
    """Returns a **deep** copy of the dictionary without a key."""
    _dict = deepcopy(dictionary)
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}
Nikita
fuente
21

... ¿cómo puedo eliminar un elemento de un diccionario para devolver una copia (es decir, sin modificar el original)?

A dictes la estructura de datos incorrecta para usar para esto.

Claro, copiar el dict y el estallido de la copia funciona, y también lo hace construir un nuevo dict con una comprensión, pero toda esa copia lleva tiempo: ha reemplazado una operación de tiempo constante por una de tiempo lineal. Y todas esas copias vivas a la vez ocupan espacio: espacio lineal por copia.

Otras estructuras de datos, como los intentos de mapeo de matriz hash , están diseñados para este tipo de caso de uso: agregar o eliminar un elemento devuelve una copia en tiempo logarítmico, compartiendo la mayor parte de su almacenamiento con el original . 1

Por supuesto que hay algunos inconvenientes. El rendimiento es logarítmico en lugar de constante (aunque con una base grande, generalmente 32-128). Y, si bien puede hacer que la API no mutante sea idéntica dict, la API "mutante" es obviamente diferente. Y, sobre todo, no hay baterías HAMT incluidas con Python. 2

La pyrsistentbiblioteca es una implementación bastante sólida de reemplazos dict basados ​​en HAMT (y varios otros tipos) para Python. Incluso tiene una ingeniosa API de evolución para portar código mutante existente a código persistente de la manera más fluida posible. Pero si desea ser explícito acerca de devolver copias en lugar de mutar, simplemente utilícelo de esta manera:

>>> from pyrsistent import m
>>> d1 = m(a=1, b=2)
>>> d2 = d1.set('c', 3)
>>> d3 = d1.remove('a')
>>> d1
pmap({'a': 1, 'b': 2})
>>> d2
pmap({'c': 3, 'a': 1, 'b': 2})
>>> d3
pmap({'b': 2})

Eso d3 = d1.remove('a')es exactamente lo que pide la pregunta.

Si tiene estructuras de datos mutables como dicte listincrustadas en el pmap, aún tendrá problemas de alias: solo puede solucionarlo si se vuelve inmutable hasta el fondo, incrustando pmapsy pvectors.


1. Los HAMT también se han vuelto populares en lenguajes como Scala, Clojure, Haskell porque juegan muy bien con la programación sin bloqueo y la memoria transaccional de software, pero ninguno de los dos es muy relevante en Python.

2. De hecho, no es un HAMT en el stdlib, que se utiliza en la ejecución de contextvars. El PEP retirado anteriormente explica por qué. Pero este es un detalle de implementación oculto de la biblioteca, no un tipo de colección pública.

abarnert
fuente
19
d = {1: 2, '2': 3, 5: 7}
del d[5]
print 'd = ', d

Resultado: d = {1: 2, '2': 3}

satels
fuente
14

Simplemente llame a del d ['clave'].

Sin embargo, en producción, siempre es una buena práctica verificar si existe 'clave' en d.

if 'key' in d:
    del d['key']
Khanh Hua
fuente
77
Hmm, no, en producción es mejor seguir la ideología de EAFP . Simplemente quite la llave en el try-exceptbloque. Al menos, esta será una operación atómica;)
maxkoryukov
1
Y si quieres ser conciso d.pop('key', None), úsalo, es una línea. Pero la pregunta real era sobre obtener el diccionario sin una clave, y no sobre modificar el dict. Entonces las comprensiones - es una buena opción aquí;)
maxkoryukov
7

No, no hay otra manera que

def dictMinus(dct, val):
   copy = dct.copy()
   del copy[val]
   return copy

Sin embargo, a menudo crear copias de diccionarios ligeramente alterados probablemente no sea una buena idea porque generará demandas de memoria relativamente grandes. Por lo general, es mejor registrar el diccionario antiguo (si es necesario) y luego modificarlo.

phihag
fuente
7
# mutate/remove with a default
ret_val = body.pop('key', 5)
# no mutation with a default
ret_val = body.get('key', 5)
daino3
fuente
5
>>> def delete_key(dict, key):
...     del dict[key]
...     return dict
... 
>>> test_dict = {'one': 1, 'two' : 2}
>>> print delete_key(test_dict, 'two')
{'one': 1}
>>>

esto no hace ningún manejo de errores, asume que la clave está en el dict, es posible que desee verificar eso primero y raisesi no

tMC
fuente
10
¿En qué se diferencia su método de simplemente del test_dict[key]?
Físico loco
5

Aquí un enfoque de diseño de nivel superior:

def eraseElement(d,k):
    if isinstance(d, dict):
        if k in d:
            d.pop(k)
            print(d)
        else:
            print("Cannot find matching key")
    else:
        print("Not able to delete")


exp = {'A':34, 'B':55, 'C':87}
eraseElement(exp, 'C')

Estoy pasando el diccionario y la clave que quiero a mi función, valida si es un diccionario y si la clave está bien, y si ambos existen, elimina el valor del diccionario e imprime los sobrantes.

Salida: {'B': 55, 'A': 34}

¡Espero que ayude!

atlas
fuente
3

A continuación, el fragmento de código lo ayudará definitivamente, he agregado comentarios en cada línea que lo ayudarán a comprender el código.

def execute():
   dic = {'a':1,'b':2}
   dic2 = remove_key_from_dict(dic, 'b')  
   print(dict2)           # {'a': 1}
   print(dict)            # {'a':1,'b':2}

def remove_key_from_dict(dictionary_to_use, key_to_delete):
   copy_of_dict = dict(dictionary_to_use)     # creating clone/copy of the dictionary
   if key_to_delete in copy_of_dict :         # checking given key is present in the dictionary
       del copy_of_dict [key_to_delete]       # deleting the key from the dictionary 
   return copy_of_dict                        # returning the final dictionary

o también puedes usar dict.pop ()

d = {"a": 1, "b": 2}

res = d.pop("c")  # No `KeyError` here
print (res)       # this line will not execute

o el mejor enfoque es

res = d.pop("c", "key not found")
print (res)   # key not found
print (d)     # {"a": 1, "b": 2}

res = d.pop("b", "key not found")
print (res)   # 2
print (d)     # {"a": 1}
Mayur Agarwal
fuente
2

Aquí hay otra variación usando la comprensión de la lista:

original_d = {'a': None, 'b': 'Some'}
d = dict((k,v) for k, v in original_d.iteritems() if v)
# result should be {'b': 'Some'}

El enfoque se basa en una respuesta de esta publicación: forma eficiente de eliminar claves con cadenas vacías de un dict

BigBlueHat
fuente
1
Si va a responder una pregunta de hace años que ya tiene una respuesta simple, apropiada y aceptada, al menos asegúrese de que su respuesta sea correcta. Esto no hace lo que pidió el OP.
user2357112 es compatible con Monica el
En general, no verifico las fechas de las preguntas que creo que podrían agregarles información valiosa. Además, según uno de los comentarios sobre la pregunta a la que me vinculé: "Por lo general, esto es exactamente lo que alguien quiere y es probablemente lo que el OP necesita, pero no es lo que el OP solicitó" stackoverflow.com/questions/12118695/… I sabía que no era una respuesta directa a la pregunta; más bien una expansión de las opciones.
BigBlueHat
3
Esta respuesta, aunque no es completa, nos permite saber que también podemos eliminar elementos con condiciones. simplemente cambiando if va if k is not 'a'respuestas el op. Pero no creo que sea una forma eficiente, esto elimina el elemento en O (n) en lugar de O (log n) como lo hace pop o del.
holgac
0
    species = {'HI': {'1': (1215.671, 0.41600000000000004),
  '10': (919.351, 0.0012),
  '1025': (1025.722, 0.0791),
  '11': (918.129, 0.0009199999999999999),
  '12': (917.181, 0.000723),
  '1215': (1215.671, 0.41600000000000004),
  '13': (916.429, 0.0005769999999999999),
  '14': (915.824, 0.000468),
  '15': (915.329, 0.00038500000000000003),
 'CII': {'1036': (1036.3367, 0.11900000000000001), '1334': (1334.532, 0.129)}}

El siguiente código hará una copia de dict speciesy eliminará elementos que no están entrans_HI

trans_HI=['1025','1215']
for transition in species['HI'].copy().keys():
    if transition not in trans_HI:
        species['HI'].pop(transition)
Sameeresque
fuente