@ -5,10 +5,12 @@
""" Generic utils. """
import codecs
import collections
import contextlib
import cStringIO
import datetime
import logging
import operator
import os
import pipes
import platform
@ -1236,3 +1238,67 @@ def FindExecutable(executable):
if os . path . isfile ( alt_target ) and os . access ( alt_target , os . X_OK ) :
return alt_target
return None
def freeze ( obj ) :
""" Takes a generic object ``obj``, and returns an immutable version of it.
Supported types :
* dict / OrderedDict - > FrozenDict
* list - > tuple
* set - > frozenset
* any object with a working __hash__ implementation ( assumes that hashable
means immutable )
Will raise TypeError if you pass an object which is not hashable .
"""
if isinstance ( obj , dict ) :
return FrozenDict ( ( freeze ( k ) , freeze ( v ) ) for k , v in obj . iteritems ( ) )
elif isinstance ( obj , ( list , tuple ) ) :
return tuple ( freeze ( i ) for i in obj )
elif isinstance ( obj , set ) :
return frozenset ( freeze ( i ) for i in obj )
else :
hash ( obj )
return obj
class FrozenDict ( collections . Mapping ) :
""" An immutable OrderedDict.
Modified From : http : / / stackoverflow . com / a / 2704866
"""
def __init__ ( self , * args , * * kwargs ) :
self . _d = collections . OrderedDict ( * args , * * kwargs )
# Calculate the hash immediately so that we know all the items are
# hashable too.
self . _hash = reduce ( operator . xor ,
( hash ( i ) for i in enumerate ( self . _d . iteritems ( ) ) ) , 0 )
def __eq__ ( self , other ) :
if not isinstance ( other , collections . Mapping ) :
return NotImplemented
if self is other :
return True
if len ( self ) != len ( other ) :
return False
for k , v in self . iteritems ( ) :
if k not in other or other [ k ] != v :
return False
return True
def __iter__ ( self ) :
return iter ( self . _d )
def __len__ ( self ) :
return len ( self . _d )
def __getitem__ ( self , key ) :
return self . _d [ key ]
def __hash__ ( self ) :
return self . _hash
def __repr__ ( self ) :
return ' FrozenDict( %r ) ' % ( self . _d . items ( ) , )