This is my original attempt, before posting the question.
Keeping it here, as it may help explain the goal.
It also has some code that would be useful if one wants to MODIFY an existing LARGE collection, rather than duplicating the data into a NEW collection.
(The other answers create new collections.)
# ---------- 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
Output:
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}
Key points:
if issubclass(t, basestring): avoids searching inside of strings, as that makes no sense, AFAIK.
if issubclass(t, tuple): converts the result back to a tuple.
For dictionaries, copy.copy(data) is used, to return an object of the same type as the original dictionary.
LIMITATION: Does not attempt to preserve collection/iterator type for types other than: list, tuple, dict (& its subclasses).
Default usage copies data structures, if a change is needed. Passing in False for beImmutable can result in higher performance when a LOT of data, but will alter the original data, including altering nested pieces of the data -- which might be referenced by variables elsewhere in your code.
(None, None)appears as a key?key = StripNones(key)BEFORE putting it into the dictionary, rather than trying to fix it afterwards.