python - recorrer - Manera eficiente de eliminar claves con cadenas vacías de un dict
lista de diccionarios python (13)
Algunos puntos de referencia:
1. Compila la lista para recrear el dict
In [7]: %%timeit dic = {str(i):i for i in xrange(10)}; dic[''10''] = None; dic[''5''] = None
...: dic = {k: v for k, v in dic.items() if v is not None}
1000000 loops, best of 7: 375 ns per loop
2. Compila la comprensión recrea el dict usando dict ()
In [8]: %%timeit dic = {str(i):i for i in xrange(10)}; dic[''10''] = None; dic[''5''] = None
...: dic = dict((k, v) for k, v in dic.items() if v is not None)
1000000 loops, best of 7: 681 ns per loop
3. Teclee y borre la clave si v es Ninguno
In [10]: %%timeit dic = {str(i):i for i in xrange(10)}; dic[''10''] = None; dic[''5''] = None
...: for k, v in dic.items():
...: if v is None:
...: del dic[k]
...:
10000000 loops, best of 7: 160 ns per loop
entonces loop y delete es el más rápido en 160ns, la comprensión de la lista es la mitad de lenta en ~ 375ns y con una llamada a dict()
es la mitad de lenta de nuevo ~ 680ns.
Al envolver 3 en una función, vuelve a bajar a aproximadamente 275ns. También para mí PyPy era aproximadamente dos veces más rápido que neet python.
Tengo un dict y me gustaría eliminar todas las claves para las que hay cadenas de valores vacías.
metadata = {u''Composite:PreviewImage'': u''(Binary data 101973 bytes)'',
u''EXIF:CFAPattern2'': u''''}
¿Cuál es la mejor manera de hacer esto?
Respuesta rápida (TL; DR)
Ejemplo01
### example01 -------------------
mydict = { "alpha":0,
"bravo":"0",
"charlie":"three",
"delta":[],
"echo":False,
"foxy":"False",
"golf":"",
"hotel":" ",
}
newdict = dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(vdata) ])
print newdict
### result01 -------------------
result01 =''''''
{''foxy'': ''False'', ''charlie'': ''three'', ''bravo'': ''0''}
''''''
Respuesta detallada
Problema
- Contexto: Python 2.x
- Escenario: el desarrollador desea modificar un diccionario para excluir valores en blanco
- también eliminar valores vacíos de un diccionario
- aka borrar claves con valores en blanco
- también conocido como diccionario de filtro para valores que no estén en blanco sobre cada par clave-valor
Solución
- example01 usa la sintaxis python list-understanding con conditional simple para eliminar valores "vacíos"
Trampas
- example01 solo opera en una copia del diccionario original (no se modifica en su lugar)
- example01 puede producir resultados inesperados dependiendo de lo que el desarrollador signifique por "vacío"
- ¿El desarrollador quiere mantener valores falsy ?
- Si no se garantiza que los valores en el diccionario sean cadenas, el desarrollador puede perder datos inesperadamente.
- result01 muestra que solo se conservaron tres pares clave-valor del conjunto original
Ejemplo alternativo
- example02 ayuda a lidiar con peligros potenciales
- El enfoque es usar una definición más precisa de "vacío" cambiando el condicional.
- Aquí solo queremos filtrar valores que evalúan cadenas en blanco.
- Aquí también usamos .strip () para filtrar valores que constan solo de espacios en blanco.
Ejemplo02
### example02 -------------------
mydict = { "alpha":0,
"bravo":"0",
"charlie":"three",
"delta":[],
"echo":False,
"foxy":"False",
"golf":"",
"hotel":" ",
}
newdict = dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(str(vdata).strip()) ])
print newdict
### result02 -------------------
result02 =''''''
{''charlie'': ''three'', ''echo'': False,
''foxy'': ''False'', ''delta'': [],
''bravo'': ''0'', ''alpha'': 0
}
''''''
Ver también
- list-comprehension
- falsy
- buscando cadena vacía
- modificando el diccionario original en su lugar
- comprensión del diccionario
- trampas de comprobar si hay una cuerda vacía
Basándose en las respuestas de patriciasz y nneonneo , y teniendo en cuenta la posibilidad de que desee eliminar claves que solo tienen ciertas cosas falsas (por ejemplo, ''''
) pero no otras (por ejemplo 0
), o tal vez incluso quiera incluir algunas cosas verdaderas (por ejemplo, ''SPAM''
), entonces podrías hacer una lista de hits altamente específica:
unwanted = ['''', u'''', None, False, [], ''SPAM'']
Desafortunadamente, esto no funciona, porque por ejemplo 0 in unwanted
evaluaciones 0 in unwanted
convierte en True
. Necesitamos discriminar entre 0
y otras cosas falsy, así que tenemos que usar is
:
any([0 is i for i in unwanted])
... evalúa a False
.
Ahora úsalo para del
las cosas no deseadas:
unwanted_keys = [k for k, v in metadata.items() if any([v is i for i in unwanted])]
for k in unwanted_keys: del metadata[k]
Si desea un diccionario nuevo, en lugar de modificar los metadata
en su lugar:
newdict = {k: v for k, v in metadata.items() if not any([v is i for i in unwanted])}
Basado en la solución de Ryan , si también tiene listas y diccionarios anidados:
Para Python 2:
def remove_empty_from_dict(d):
if type(d) is dict:
return dict((k, remove_empty_from_dict(v)) for k, v in d.iteritems() if v and remove_empty_from_dict(v))
elif type(d) is list:
return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
else:
return d
Para Python 3:
def remove_empty_from_dict(d):
if type(d) is dict:
return dict((k, remove_empty_from_dict(v)) for k, v in d.items() if v and remove_empty_from_dict(v))
elif type(d) is list:
return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
else:
return d
Leí todas las respuestas en este hilo y algunas referidas también a este hilo: elimine los dicts vacíos en el diccionario anidado con función recursiva
Originalmente usé la solución aquí y funcionó de maravilla:
Intento 1: Demasiado caliente (sin rendimiento o a prueba de futuro) : def scrub_dict(d): if type(d) is dict: return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v)) else: return d
Pero algunos problemas de rendimiento y compatibilidad se plantearon en el mundo de Python 2.7:
- use
isinstance
lugar detype
- Desenrollar la compilación de lista en
for
loop para la eficiencia - usa elementos seguros de
iteritems
lugar deiteritems
Intento 2: Demasiado frío (falta de memoria) : def scrub_dict(d): new_dict = {} for k, v in d.items(): if isinstance(v,dict): v = scrub_dict(v) if not v in (u'''', None, {}): new_dict[k] = v return new_dict
DOH! Esto no es recursivo y para nada memorable.
Intento 3: Just Right (hasta ahora) : def scrub_dict(d): new_dict = {} for k, v in d.items(): if isinstance(v,dict): v = scrub_dict(v) if not v in (u'''', None, {}): new_dict[k] = v return new_dict
Para python 3
dict((k, v) for k, v in metadata.items() if v)
Puede ser aún más corto que la solución de BrenBarn (y más legible, creo)
{k: v for k, v in metadata.items() if v}
Probado con Python 2.7.3.
Python 2.X
dict((k, v) for k, v in metadata.iteritems() if v)
Python 3.X
{k: v for k, v in metadata.items() if v is not None}
Tenga en cuenta que todas sus claves tienen valores. Es solo que algunos de esos valores son la cadena vacía. No existe una clave en un dict sin valor; si no tuviera un valor, no estaría en el dict.
Si desea un enfoque completo, pero conciso para manejar estructuras de datos del mundo real que a menudo están anidadas, e incluso puede contener ciclos, le recomiendo consultar la utilidad de reasignación del paquete de utilidades boltons .
Luego de pip install boltons
o copiar iterutils.py en su proyecto, solo haga lo siguiente:
from boltons.iterutils import remap
drop_falsey = lambda path, key, value: bool(value)
clean = remap(metadata, visit=drop_falsey)
Esta página tiene muchos más ejemplos, incluidos los que trabajan con objetos mucho más grandes de la API de Github.
Es puro-Python, por lo que funciona en todas partes, y está totalmente probado en Python 2.7 y 3.3+. Lo mejor de todo es que lo escribí exactamente para casos como este, así que si encuentras un caso que no funciona, puedes echarme una broma para arreglarlo aquí mismo .
Si realmente necesita modificar el diccionario original:
empty_keys = [k for k,v in metadata.iteritems() if not v]
for k in empty_keys:
del metadata[k]
Tenga en cuenta que debemos hacer una lista de las claves vacías porque no podemos modificar un diccionario mientras lo recorremos (como habrá notado). Sin embargo, esto es menos costoso (en cuanto a la memoria) que crear un diccionario completamente nuevo, a menos que haya muchas entradas con valores vacíos.
Si tiene un diccionario anidado y desea que funcione incluso para elementos secundarios vacíos, puede usar una variante recursiva de la sugerencia de BrenBarn:
def scrub_dict(d):
if type(d) is dict:
return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v))
else:
return d
Una forma alternativa de hacerlo, es usar la comprensión del diccionario. Esto debería ser compatible con 2.7+
result = {
key: value for key, value in
{"foo": "bar", "lorem": None}.items()
if value
}
La solución de BrenBarn es ideal (y pythonic, podría agregar). Aquí hay otra solución (fp), sin embargo:
from operator import itemgetter
dict(filter(itemgetter(1), metadata.items()))