¿Filtro dict para contener solo ciertas claves?

497

Tengo un dictque tiene un montón de entradas. Solo estoy interesado en unos pocos de ellos. ¿Hay una manera fácil de eliminar a todos los demás?

mpen
fuente
Es útil decir qué tipo de claves (enteros? Cadenas? Fechas? Objetos arbitrarios?) Y, por lo tanto, si hay una prueba simple (cadena, expresión regular, membresía de lista o desigualdad numérica) para verificar qué teclas están dentro o fuera. O de lo contrario, necesitamos llamar a una función arbitraria (s) para determinar eso.
smci
@smci Teclas de cadena. No pienses que incluso se me ocurrió que podría usar cualquier otra cosa; Llevo tanto tiempo codificando en JS y PHP ...
mpen

Respuestas:

656

Construyendo un nuevo dict:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Utiliza la comprensión del diccionario.

Si utiliza una versión que carece de ellas (es decir, Python 2.6 y versiones anteriores), hágalo dict((your_key, old_dict[your_key]) for ...). Es lo mismo, aunque más feo.

Tenga en cuenta que esto, a diferencia de la versión de jnnnnn, tiene un rendimiento estable (depende solo del número de your_keys) para old_dict s de cualquier tamaño. Ambos en términos de velocidad y memoria. Como se trata de una expresión generadora, procesa un elemento a la vez y no examina todos los elementos de old_dict.

Eliminar todo en su lugar:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]
Comunidad
fuente
8
"Utiliza la comprensión del diccionario, si usa una versión que carece de ellas" == versión <= 2.6
getekha
8
Lanza un KeyError si una de las claves del archivador no está presente en old_dict. Sugeriría {k: d [k] para k en filtro si k en d}
Peter Gibson
1
@PeterGibson Sí, si eso es parte de los requisitos, debe hacer algo al respecto. Si está soltando las claves en silencio, agregando un valor predeterminado u otra cosa, depende de lo que esté haciendo; Hay muchos casos de uso en los que su enfoque es incorrecto. También hay muchos en los que falta una clave old_dictindica un error en otro lugar, y en ese caso prefiero un error a resultados silenciosamente incorrectos.
@delnan, también la adición "if k in d" te ralentiza si d es grande, solo pensé que valía la pena mencionarlo
Peter Gibson
77
@ PeterGibson No lo hace, la búsqueda del diccionario es O (1).
130

Ligeramente más elegante comprensión dict:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}
Ransford
fuente
Votado Estaba pensando en agregar una respuesta similar a esta. Sin embargo, solo por curiosidad, ¿por qué {k: v para k, v en dict.items () ...} en lugar de {k: dict [k] para k en dict ...} ¿Hay alguna diferencia de rendimiento?
Hart Simha
44
Respondí mi propia pregunta. El {k: dict [k] para k en dict ...} es aproximadamente un 20-25% más rápido, al menos en Python 2.7.6, con un diccionario de 26 elementos (timeit (..., setup = "d = {chr (x + 97): x + 1 para x en el rango (26)} ")), dependiendo de cuántos elementos se estén filtrando (filtrar las teclas de consonantes es más rápido que filtrar las teclas de las vocales porque estás mirando hacia arriba menos artículos). La diferencia en el rendimiento puede volverse menos significativa a medida que crece el tamaño del diccionario.
Hart Simha
55
Probablemente sería el mismo rendimiento si lo usaras en su mydict.iteritems()lugar. .items()crea otra lista
Pat
64

Aquí hay un ejemplo en Python 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

La parte de filtrado es la ifdeclaración.

Este método es más lento que la respuesta de delnan si solo desea seleccionar algunas de las muchas teclas.

jnnnnn
fuente
11
excepto que probablemente usaría if key in ('x','y','z'), supongo.
mpen
si ya sabe qué teclas desea, use la respuesta de delnan. Si necesita probar cada clave con una declaración if, use la respuesta de ransford.
jnnnnn
1
Esta solución tiene una ventaja más. Si el diccionario se devuelve de una llamada de función costosa (es decir, a / old_dict es una llamada de función), esta solución llama a la función solo una vez. En un entorno imperativo, almacenar el diccionario devuelto por la función en una variable no es un gran problema, pero en un entorno funcional (por ejemplo, en una lambda), esta es una observación clave.
gae123
21

Puede hacerlo con la función de proyecto de mi biblioteca funcy :

from funcy import project
small_dict = project(big_dict, keys)

También eche un vistazo a select_keys .

Suor
fuente
20

Código 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Código 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Código 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Todas las partes del rendimiento del código se miden con timeit usando number = 1000, y se recopilan 1000 veces para cada fragmento de código.

ingrese la descripción de la imagen aquí

Para Python 3.6, el rendimiento de tres formas de filtrar claves de dictado es casi el mismo. Para python 2.7, el código 3 es un poco más rápido.

YY
fuente
por curiosidad, ¿hiciste esa trama desde Python?
user5359531
1
ggplot2 en R - parte de tidyverse
keithpjolley
18

Este liner lambda debería funcionar:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Aquí hay un ejemplo:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

Es una comprensión básica de la lista que itera sobre sus claves de dictado (i en x) y genera una lista de pares de tuplas (clave, valor) si la clave vive en la lista de claves deseada (y). Un dict () envuelve todo para que salga como un objeto dict.

Jim
fuente
Debería usar un setfor wanted_keys, pero por lo demás se ve bien.
mpen
Esto me da un diccionario en blanco si mi diccionario original contiene listas en lugar de valores. ¿Alguna solución?
FaCoffee
@Francesco, ¿puedes dar un ejemplo? Si ejecuto:, dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z'))vuelve {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}como estaba previsto.
Jim
Intenté esto con: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}y el resultado fue {}, que supuse que era un dict en blanco.
FaCoffee
Una cosa, "dict" es una palabra reservada, por lo que no debe usarla para nombrar un dict. ¿Cuáles fueron las llaves que intentabas sacar? Si ejecuto:, foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2'))obtengo: {'0': [1, 3], '2': [1, 4]}cuál es el resultado esperado
Jim
14

Dado su diccionario original origy el conjunto de entradas que le interesan keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

que no es tan bueno como la respuesta de delnan, pero debería funcionar en todas las versiones de Python de interés. Sin embargo, es frágil para cada elemento keysexistente en su diccionario original.

Kai
fuente
Bueno, esta es básicamente una versión entusiasta de la "versión del generador de tuplas" de mi comprensión dict. Muy compatible, aunque las expresiones generadoras se introdujeron en 2.4, primavera de 2005. En serio, ¿alguien sigue usando esto?
1
No estoy en desacuerdo; 2.3 realmente ya no debería existir. Sin embargo, como una encuesta obsoleta de uso 2.3: moinmo.in/PollAboutRequiringPython24 Versión corta: RHEL4, SLES9, enviado con OS X 10.4
Kai
7

Basado en la respuesta aceptada por delnan.

¿Qué pasa si una de sus claves deseadas no está en el old_dict? La solución delnan arrojará una excepción KeyError que puede atrapar. Si eso no es lo que necesitas, tal vez quieras:

  1. solo incluya claves que se eliminen tanto en old_dict como en su conjunto de wanted_keys.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. tiene un valor predeterminado para las claves que no está establecido en old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}
MyGGaN
fuente
También podrías hacerlo{k: old_dict.get(k, default) for k in ...}
Moberg el
6

Esta función hará el truco:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Al igual que la versión de delnan, esta utiliza la comprensión del diccionario y tiene un rendimiento estable para diccionarios grandes (depende solo de la cantidad de claves que permita y no de la cantidad total de claves en el diccionario).

Y al igual que la versión de MyGGan, esta permite que su lista de claves incluya claves que pueden no existir en el diccionario.

Y como beneficio adicional, aquí está el inverso, donde puede crear un diccionario excluyendo ciertas claves en el original:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Tenga en cuenta que, a diferencia de la versión de delnan, la operación no se realiza en su lugar, por lo que el rendimiento está relacionado con la cantidad de teclas en el diccionario. Sin embargo, la ventaja de esto es que la función no modificará el diccionario proporcionado.

Editar: se agregó una función separada para excluir ciertas teclas de un dict.

Ryan
fuente
Debe permitirlo keyspor cualquier tipo de iterable, como lo que acepta el conjunto .
mpen
Ah, buena llamada, gracias por señalar esto. Haré esa actualización.
Ryan
Me pregunto si estás mejor con dos funciones. Si le preguntas a 10 personas "¿ invertimplica que keysse mantiene el argumento o que keysse rechaza el argumento?", ¿Cuántos de ellos estarían de acuerdo?
skatenerd
Actualizado. Déjame saber lo que piensas.
Ryan
Esto parece no funcionar si la entrada dict tiene listas en lugar de valores. En este caso obtienes un dict vacío. ¿Alguna solución?
FaCoffee
4

Si queremos hacer un nuevo diccionario con las claves seleccionadas eliminadas, podemos hacer uso de la comprensión del diccionario.
Por ejemplo:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}
Srivastava
fuente
Ordenado. Solo funciona en Python 3. Python 2 dice "TypeError: tipos de operando no admitidos para -: 'list' y 'set'"
mpen
Conjunto agregado (d.keys ()) para Python 2. Esto funciona cuando ejecuto.
Srivastava
2

Otra opción:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Pero obtienes un list(Python 2) o un iterador (Python 3) devuelto por filter(), no a dict.

marsl
fuente
Envoltura filtereden dicty obtiene nuevamente el diccionario!
CMCDragonkai
1

Forma corta:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Como la mayoría de las respuestas sugieren para mantener la concisión, tenemos que crear un objeto duplicado ya sea un listo dict. Este crea un descarte listpero elimina las claves en original dict.

nehem
fuente
0

Aquí hay otro método simple que se utiliza delen un revestimiento:

for key in e_keys: del your_dict[key]

e_keyses la lista de las claves que se excluirán. Actualizará su dict en lugar de darle uno nuevo.

Si desea un nuevo dict de salida, haga una copia del dict antes de eliminar:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]
Trueno Negro
fuente
0

Podrías usar python-benedict , es una subclase dict.

Instalación: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

Es de código abierto en GitHub: https://github.com/fabiocaccamo/python-benedict


Descargo de responsabilidad: soy el autor de esta biblioteca.

Fabio Caccamo
fuente