The method works here.
You cannot fix these immutable objects, but what you can do is use the proxy functions instead of circular links and make them look for a real function in the global dictionary.
1: When serializing, keep track of all the features you saw. If you see the same thing again, do not re-serialize, but serialize the control value.
I used the set:
lfunctionHashes = set()
and for each serialized element, check if it is in the set, go with the sentinel, if so, otherwise add it to the set and the marshal is correct:
lhash = hash(litem) if lhash in lfunctionHashes: lmarshalledClosureValues.append([lhash, None]) else: lfunctionHashes.add(lhash) lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__])
2: when deserializing, hold the global dict of functionhash: function
gfunctions = {}
During deserialization, every time you deserialize a function, add it to gfunctions. Here's the element (hash, code, shortvalues, modulename):
lfunction = types.FunctionType(marshal.loads(item[1]), globals, closure=UnmarshalClosureValues(item[2])) gfunctions[item[0]] = lfunction
And when you come across a control value for a function, use a proxy server, passing the hash of the function:
lfunction = make_proxy(item[0])
Here is the proxy. It is looking for a real hash based function:
def make_proxy(f_hash): def f_proxy(*args, **kwargs): global gfunctions f = lfunctions[f_hash] f(*args, **kwargs) return f_proxy
I also had to make a few more changes:
- I used pickle instead of marshal in some places, could study it further
- I include the module name in serialization, as well as code and closure, so I can find the correct global functions for the function when deserializing.
- In deserialization, the length of the tuple tells you that you are deserializing: 1 for a simple value, 2 for a function that requires proxying, 4 for a fully serialized function
Here is the complete new code.
lfunctions = {} def DeserialiseFunction(aSerialisedFunction): lmarshalledFunc, lmarshalledClosureValues, lmoduleName = pickle.loads(aSerialisedFunction) lglobals = sys.modules[lmoduleName].__dict__ lglobals["lfunctions"] = lfunctions def make_proxy(f_hash): def f_proxy(*args, **kwargs): global lfunctions f = lfunctions[f_hash] f(*args, **kwargs) return f_proxy def make_cell(value): return (lambda x: lambda: x)(value).func_closure[0] def UnmarshalClosureValues(aMarshalledClosureValues): global lfunctions lclosure = None if aMarshalledClosureValues: lclosureValues = [] for item in aMarshalledClosureValues: ltype = len(item) if ltype == 1: lclosureValues.append(pickle.loads(item[0])) elif ltype == 2: lfunction = make_proxy(item[0]) lclosureValues.append(lfunction) elif ltype == 4: lfuncglobals = sys.modules[item[3]].__dict__ lfuncglobals["lfunctions"] = lfunctions lfunction = types.FunctionType(marshal.loads(item[1]), lfuncglobals, closure=UnmarshalClosureValues(item[2])) lfunctions[item[0]] = lfunction lclosureValues.append(lfunction) lclosure = tuple([make_cell(lvalue) for lvalue in lclosureValues]) return lclosure lfunctionCode = marshal.loads(lmarshalledFunc) lclosure = UnmarshalClosureValues(lmarshalledClosureValues) lfunction = types.FunctionType(lfunctionCode, lglobals, closure=lclosure) return lfunction def SerialiseFunction(aFunction): if not aFunction or not hasattr(aFunction, "func_code"): raise Exception ("First argument required, must be a function") lfunctionHashes = set() def MarshalClosureValues(aClosure, aParentIndices = []): lmarshalledClosureValues = [] if aClosure: lclosureValues = [lcell.cell_contents for lcell in aClosure] lmarshalledClosureValues = [] for index, litem in enumerate(lclosureValues): lfullIndex = list(aParentIndices) lfullIndex.append(index) if isinstance(litem, types.FunctionType): lhash = hash(litem) if lhash in lfunctionHashes: lmarshalledClosureValues.append([lhash, None]) else: lfunctionHashes.add(lhash) lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__]) else: lmarshalledClosureValues.append([pickle.dumps(litem)]) lmarshalledFunc = marshal.dumps(aFunction.func_code) lmarshalledClosureValues = MarshalClosureValues(aFunction.func_closure) lmoduleName = aFunction.__module__ lcombined = (lmarshalledFunc, lmarshalledClosureValues, lmoduleName) retval = pickle.dumps(lcombined) return retval