You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AttributeError when pickled function uses submodules #78
This problem is when a function refers (by attribute) to a sub-module of a package. Cloudpickle appears to pickle functions not by name, but by code plus (a subset of) globals. So the parent package is injected into the pickle, but the sub-module is not.
This leads to non-intuitive bugs in applications such as cluster computing (e.g. with dask.distributed).
Workarounds:
perform imports inside functions (contrary to PEP8).
import sub-modules (or their contents) as globals.
ensure (somehow) that all relevant sub-modules are automatically imported by respective parent packages (e.g. __init__.py).
arrange for the unpickling process to have already done an import of the sub-module (e.g. by first uncloudpickling something else that did refer to the sub-module as a global).
I assume cloudpickle checks whether a global is an imported module, and if so then stores the name (rather than pickling its attributes). Is it practical to also check (via sys.modules.keys()) which sub-modules had previously been imported, and ensure every such module is subsequently initialised?
Alternatively, since the above might be overzealous, could instead in CloudPickler.extract_func_data do something roughly like replace f_globals = {} with:
I'm thinking along the lines of a change to cloudpickle.CloudPickler.save_function_tuple.
importcloudpickleimportunittest.mockunpatched=cloudpickle.CloudPickler.extract_func_datadefpicklepatch(self, func):
code, f_globals, defaults, closure, dct, base_globals=unpatched(self, func)
# check if any top-level dependency is an imported packageforxinset(f_globals.values()) |set(closure):
ifisinstance(x, cloudpickle.types.ModuleType) andx.__package__:
# check if the package has any currently loaded sub-importsprefix=x.__name__+'.'forname, moduleincloudpickle.sys.modules.items():
ifname.startswith(prefix):
# check whether the function can address the sub-moduletokens=set(name[len(prefix):].split('.'))
ifnottokens-set(code.co_names):
self.save(module) # ensure the unpickler executes import of this submoduleself.write(cloudpickle.pickle.POP) # then discard the reference to itreturncode, f_globals, defaults, closure, dct, base_globalspatch=unittest.mock.patch('cloudpickle.CloudPickler.extract_func_data', new=picklepatch)
#def scope():# import http.cookies# def example():# x = http.HTTPStatus# x = http.cookies.Morsel# print('test successful')# return example#example = scope()importhttp.cookiesdefexample():
x=http.HTTPStatusx=http.cookies.Morselprint('test successful')
broken=cloudpickle.dumps(example)
withpatch:
fixed=cloudpickle.dumps(example)
# refresh the environment (unimport http.cookies)delhttpdelcloudpickle.sys.modules['http.cookies']
delcloudpickle.sys.modules['http']
# deserialisef=cloudpickle.loads(broken)
try:
f()
exceptAttributeError:
print('patch required')
g=cloudpickle.loads(fixed)
g()
Activity
benjimin commentedon Feb 1, 2017
For example, could modify
CloudPickler.save_moduleto includeAlternatively, since the above might be overzealous, could instead in
CloudPickler.extract_func_datado something roughly like replacef_globals = {}with:benjimin commentedon Feb 7, 2017
I'm thinking along the lines of a change to
cloudpickle.CloudPickler.save_function_tuple.Add regression tests for submodule dependence of pickled function, is…
save submodules to resolve issue cloudpipe#78
Add regression tests for submodule dependence of pickled function, is…
save submodules to resolve issue cloudpipe#78
llllllllll commentedon May 31, 2017
This is fixed in the latest release