+import re
+import cPickle as pickle
+
+
+class Visitor:
+
+ def __init__(self):
+ self.dispatch = {}
+ self.dispatch[type(None)] = self.visitNone
+ self.dispatch[bool] = self.visitBool
+ self.dispatch[int] = self.visitInt
+ self.dispatch[long] = self.visitInt
+ self.dispatch[float] = self.visitFloat
+ self.dispatch[str] = self.visitStr
+ self.dispatch[tuple] = self.visitTuple
+ self.dispatch[list] = self.visitList
+ self.dispatch[dict] = self.visitDict
+ self.dispatch[bytearray] = self.visitByteArray
+
+ def visit(self, obj):
+ method = self.dispatch.get(type(obj), self.visitObj)
+ return method(obj)
+
+ def visitObj(self, obj):
+ raise NotImplementedError
+
+ def visitAtom(self, obj):
+ return self.visitObj(obj)
+
+ def visitNone(self, obj):
+ return self.visitAtom(obj)
+
+ def visitBool(self, obj):
+ return self.visitAtom(obj)
+
+ def visitInt(self, obj):
+ return self.visitAtom(obj)
+
+ def visitFloat(self, obj):
+ return self.visitAtom(obj)
+
+ def visitStr(self, obj):
+ return self.visitAtom(obj)
+
+ def visitIterable(self, obj):
+ return self.visitObj(obj)
+
+ def visitTuple(self, obj):
+ return self.visitIterable(obj)
+
+ def visitList(self, obj):
+ return self.visitIterable(obj)
+
+ def visitDict(self, obj):
+ raise NotImplementedError
+
+ def visitByteArray(self, obj):
+ raise NotImplementedError
+
+
+class Dumper(Visitor):
+
+ id_re = re.compile('^[_A-Za-z][_A-Za-z0-9]*$')
+
+ def visitObj(self, obj):
+ return repr(obj)
+
+ def visitStr(self, obj):
+ if self.id_re.match(obj):
+ return obj
+ else:
+ return repr(obj)
+
+ def visitTuple(self, obj):
+ return '[' + ', '.join(itertools.imap(self.visit, obj)) + ']'
+
+ def visitList(self, obj):
+ return '(' + ', '.join(itertools.imap(self.visit, obj)) + ')'
+
+ def visitByteArray(self, obj):
+ return 'blob(%u)' % len(obj)
+
+
+class Hasher(Visitor):
+ '''Returns a hashable version of the objtree.'''
+
+ def visitObj(self, obj):
+ return obj
+
+ def visitAtom(self, obj):
+ return obj
+
+ def visitIterable(self, obj):
+ return tuple(itertools.imap(self.visit, obj))
+
+ def visitByteArray(self, obj):
+ return str(obj)
+
+
+class Rebuilder(Visitor):
+ '''Returns a hashable version of the objtree.'''
+
+ def visitAtom(self, obj):
+ return obj
+
+ def visitIterable(self, obj):
+ changed = False
+ newItems = []
+ for oldItem in obj:
+ newItem = self.visit(oldItem)
+ if newItem is not oldItem:
+ changed = True
+ newItems.append(newItem)
+ if changed:
+ klass = type(obj)
+ return klass(newItems)
+ else:
+ return obj
+
+ def visitByteArray(self, obj):
+ return obj