Python: ¿Cómo RECURSIVAMENTE eliminar ninguno de los valores de una estructura de datos NESTED (listas y diccionarios)?

Aquí hay algunos datos nesteds, que incluyen listas, tuplas y diccionarios:

data1 = ( 501, (None, 999), None, (None), 504 ) data2 = { 1:601, 2:None, None:603, 'four':'sixty' } data3 = OrderedDict( [(None, 401), (12, 402), (13, None), (14, data2)] ) data = [ [None, 22, tuple([None]), (None,None), None], ( (None, 202), {None:301, 32:302, 33:data1}, data3 ) ] 

Objetivo: eliminar cualquier clave o valor (de “datos”) que sean Ninguno. Si una lista o diccionario contiene un valor, eso es en sí mismo una lista, una tupla o un diccionario, entonces RECURSE, para eliminar NESTED Nones.

Salida deseada:

 [[22, (), ()], ((202,), {32: 302, 33: (501, (999,), 504)}, OrderedDict([(12, 402), (14, {'four': 'sixty', 1: 601})]))] 

O más legiblemente, aquí está la salida formateada:

 StripNones(data)= list: . [22, (), ()] . tuple: . . (202,) . . {32: 302, 33: (501, (999,), 504)} . . OrderedDict([(12, 402), (14, {'four': 'sixty', 1: 601})]) 

Propondré una posible respuesta, ya que no he encontrado una solución existente para esto. Aprecio cualquier alternativa, o punteros a soluciones preexistentes.

EDITAR Olvidé mencionar que esto tiene que funcionar en Python 2.7. No puedo usar Python 3 en este momento.

Aunque vale la pena publicar soluciones de Python 3, para otros. Así que por favor indique qué python está respondiendo.

Si puede asumir que los métodos __init__ de las diversas subclases tienen la misma firma que la clase base típica:

 def remove_none(obj): if isinstance(obj, (list, tuple, set)): return type(obj)(remove_none(x) for x in obj if x is not None) elif isinstance(obj, dict): return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None) else: return obj from collections import OrderedDict data1 = ( 501, (None, 999), None, (None), 504 ) data2 = { 1:601, 2:None, None:603, 'four':'sixty' } data3 = OrderedDict( [(None, 401), (12, 402), (13, None), (14, data2)] ) data = [ [None, 22, tuple([None]), (None,None), None], ( (None, 202), {None:301, 32:302, 33:data1}, data3 ) ] print remove_none(data) 

Tenga en cuenta que esto no funcionará con un defaultdict por ejemplo, ya que el __init__ toma y el argumento adicional a __init__ . Para que funcione con defaultdict se requeriría otro caso especial elif (antes del de los dictados regulares).


También tenga en cuenta que en realidad he construido nuevos objetos. No he modificado los viejos. Sería posible modificar los objetos antiguos si no fuera necesario admitir la modificación de objetos inmutables como tuple .

Si desea un enfoque completo, aunque conciso, para manejar estructuras de datos anidadas del mundo real como éstas, e incluso manejar ciclos, le recomiendo que consulte la utilidad de reasignación del paquete de utilidades boltons .

Después de que pip install boltons o copie iterutils.py en su proyecto, simplemente haga lo siguiente:

 from collections import OrderedDict from boltons.iterutils import remap data1 = ( 501, (None, 999), None, (None), 504 ) data2 = { 1:601, 2:None, None:603, 'four':'sixty' } data3 = OrderedDict( [(None, 401), (12, 402), (13, None), (14, data2)] ) data = [ [None, 22, tuple([None]), (None,None), None], ( (None, 202), {None:301, 32:302, 33:data1}, data3 ) ] drop_none = lambda path, key, value: key is not None and value is not None cleaned = remap(data, visit=drop_none) print(cleaned) # got: [[22, (), ()], ((202,), {32: 302, 33: (501, (999,), 504)}, OrderedDict([(12, 402), (14, {'four': 'sixty', 1: 601})]))] 

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á completamente probado en Python 2.7 y 3.3+. Lo mejor de todo, lo escribí para casos exactamente como este, así que si encuentras un caso que no maneja, puedes fastidiarme para que lo arregle aquí .

 def stripNone(data): if isinstance(data, dict): return {k:stripNone(v) for k, v in data.items() if k is not None and v is not None} elif isinstance(data, list): return [stripNone(item) for item in data if item is not None] elif isinstance(data, tuple): return tuple(stripNone(item) for item in data if item is not None) elif isinstance(data, set): return {stripNone(item) for item in data if item is not None} else: return data 

Ejecuciones de muestra:

 print stripNone(data1) print stripNone(data2) print stripNone(data3) print stripNone(data) (501, (999,), 504) {'four': 'sixty', 1: 601} {12: 402, 14: {'four': 'sixty', 1: 601}} [[22, (), ()], ((202,), {32: 302, 33: (501, (999,), 504)}, {12: 402, 14: {'four': 'sixty', 1: 601}})] 
 def purify(o): if hasattr(o, 'items'): oo = type(o)() for k in o: if k != None and o[k] != None: oo[k] = purify(o[k]) elif hasattr(o, '__iter__'): oo = [ ] for it in o: if it != None: oo.append(purify(it)) else: return o return type(o)(oo) print purify(data) 

Da:

 [[22, (), ()], ((202,), {32: 302, 33: (501, (999,), 504)}, OrderedDict([(12, 402), (14, {'four': 'sixty', 1: 601})]))] 

Este es mi bash original, antes de publicar la pregunta. Manteniéndolo aquí, ya que puede ayudar a explicar el objective.

También tiene algún código que sería útil si uno quisiera MODIFICAR una colección GRANDE existente, en lugar de duplicar los datos en una colección NUEVA. (Las otras respuestas crean nuevas colecciones.)

 # ---------- StripNones.py Python 2.7 ---------- import collections, copy # Recursively remove None, from list/tuple elements, and dict key/values. # NOTE: Changes type of iterable to list, except for strings and tuples. # NOTE: We don't RECURSE KEYS. # When "beImmutable=False", may modify "data". # Result may have different collection types; similar to "filter()". def StripNones(data, beImmutable=True): t = type(data) if issubclass(t, dict): return _StripNones_FromDict(data, beImmutable) elif issubclass(t, collections.Iterable): if issubclass(t, basestring): # Don't need to search a string for None. return data # NOTE: Changes type of iterable to list. data = [StripNones(x, beImmutable) for x in data if x is not None] if issubclass(t, tuple): return tuple(data) return data # Modifies dict, removing items whose keys are in keysToRemove. def RemoveKeys(dict, keysToRemove): for key in keysToRemove: dict.pop(key, None) # Recursively remove None, from dict key/values. # NOTE: We DON'T RECURSE KEYS. # When "beImmutable=False", may modify "data". def _StripNones_FromDict(data, beImmutable): keysToRemove = [] newItems = [] for item in data.iteritems(): key = item[0] if None in item: # Either key or value is None. keysToRemove.append( key ) else: # The value might change when stripped. oldValue = item[1] newValue = StripNones(oldValue, beImmutable) if newValue is not oldValue: newItems.append( (key, newValue) ) somethingChanged = (len(keysToRemove) > 0) or (len(newItems) > 0) if beImmutable and somethingChanged: # Avoid modifying the original. data = copy.copy(data) if len(keysToRemove) > 0: # if not beImmutable, MODIFYING ORIGINAL "data". RemoveKeys(data, keysToRemove) if len(newItems) > 0: # if not beImmutable, MODIFYING ORIGINAL "data". data.update( newItems ) return data # ---------- TESTING ---------- # When run this file as a script (instead of importing it): if (__name__ == "__main__"): from collections import OrderedDict maxWidth = 100 indentStr = '. ' def NewLineAndIndent(indent): return '\n' + indentStr*indent #print NewLineAndIndent(3) # Returns list of strings. def HeaderAndItems(value, indent=0): if isinstance(value, basestring): L = repr(value) else: if isinstance(value, dict): L = [ repr(key) + ': ' + Repr(value[key], indent+1) for key in value ] else: L = [ Repr(x, indent+1) for x in value ] header = type(value).__name__ + ':' L.insert(0, header) #print L return L def Repr(value, indent=0): result = repr(value) if (len(result) > maxWidth) and \ isinstance(value, collections.Iterable) and \ not isinstance(value, basestring): L = HeaderAndItems(value, indent) return NewLineAndIndent(indent + 1).join(L) return result #print Repr( [11, [221, 222], {'331':331, '332': {'3331':3331} }, 44] ) def printV(name, value): print( str(name) + "= " + Repr(value) ) print '\n\n\n' data1 = ( 501, (None, 999), None, (None), 504 ) data2 = { 1:601, 2:None, None:603, 'four':'sixty' } data3 = OrderedDict( [(None, 401), (12, 402), (13, None), (14, data2)] ) data = [ [None, 22, tuple([None]), (None,None), None], ( (None, 202), {None:301, 32:302, 33:data1}, data3 ) ] printV( 'ORIGINAL data', data ) printV( 'StripNones(data)', StripNones(data) ) print '----- beImmutable = True -----' #printV( 'data', data ) printV( 'data2', data2 ) #printV( 'data3', data3 ) print '----- beImmutable = False -----' StripNones(data, False) #printV( 'data', data ) printV( 'data2', data2 ) #printV( 'data3', data3 ) print 

Salida:

 ORIGINAL data= list: . [None, 22, (None,), (None, None), None] . tuple: . . (None, 202) . . {32: 302, 33: (501, (None, 999), None, None, 504), None: 301} . . OrderedDict: . . . None: 401 . . . 12: 402 . . . 13: None . . . 14: {'four': 'sixty', 1: 601, 2: None, None: 603} StripNones(data)= list: . [22, (), ()] . tuple: . . (202,) . . {32: 302, 33: (501, (999,), 504)} . . OrderedDict([(12, 402), (14, {'four': 'sixty', 1: 601})]) ----- beImmutable = True ----- data2= {'four': 'sixty', 1: 601, 2: None, None: 603} ----- beImmutable = False ----- data2= {'four': 'sixty', 1: 601} 

Puntos clave:

  • if issubclass(t, basestring): evita la búsqueda dentro de cadenas, ya que eso no tiene sentido, AFAIK.

  • if issubclass(t, tuple): convierte el resultado de nuevo en una tupla.

  • Para los diccionarios, se copy.copy(data) , para devolver un objeto del mismo tipo que el diccionario original.

  • LIMITACIÓN: No intenta preservar el tipo de colección / iterador para tipos distintos a: lista, tupla, dict (y sus subclases).

  • El uso predeterminado copia estructuras de datos, si se necesita un cambio. Pasar False para beImmutable puede resultar en un mayor rendimiento cuando beImmutable información, pero alterará la información original, incluida la alteración de fragmentos de datos nesteds, a los que se puede hacer referencia en otras variables de su código.