Unable to load files using pickle and multiple modules
The issue is that you're pickling objects defined in Settings by actually running the 'Settings' module, then you're trying to unpickle the objects from the GUI
module.
Remember that pickle doesn't actually store information about how a class/object is constructed, and needs access to the class when unpickling. See wiki on using Pickle for more details.
In the pkl data, you see that the object being referenced is __main__.Manager
, as the 'Settings' module was main when you created the pickle file (i.e. you ran the 'Settings' module as the main script to invoke the addUser
function).
Then, you try unpickling in 'Gui' - so that module has the name __main__
, and you're importing Setting within that module. So of course the Manager class will actually be Settings.Manager
. But the pkl file doesn't know this, and looks for the Manager class within __main__
, and throws an AttributeError because it doesn't exist (Settings.Manager
does, but __main__.Manager
doesn't).
Here's a minimal code set to demonstrate.
The class_def.py
module:
import pickle
class Foo(object):
def __init__(self, name):
self.name = name
def main():
foo = Foo('a')
with open('test_data.pkl', 'wb') as f:
pickle.dump([foo], f, -1)
if __name__=='__main__':
main()
You run the above to generate the pickle data.
The main_module.py
module:
import pickle
import class_def
if __name__=='__main__':
with open('test_data.pkl', 'rb') as f:
users = pickle.load(f)
You run the above to attempt to open the pickle file, and this throws roughly the same error that you were seeing. (Slightly different, but I'm guessing that's because I'm on Python 2.7)
The solution is either:
- You make the class available within the namespace of the top-level module (i.e. GUI or main_module) through an explicit import, or
- You create the pickle file from the same top-level module as the one that you will open it in (i.e. call
Settings.addUser
from GUI, orclass_def.main
from main_module). This means that the pkl file will save the objects asSettings.Manager
orclass_def.Foo
, which can then be found in theGUI
`main_module` namespace.
Option 1 example:
import pickle
import class_def
from class_def import Foo # Import Foo into main_module's namespace explicitly
if __name__=='__main__':
with open('test_data.pkl', 'rb') as f:
users = pickle.load(f)
Option 2 example:
import pickle
import class_def
if __name__=='__main__':
class_def.main() # Objects are being pickled with main_module as the top-level
with open('test_data.pkl', 'rb') as f:
users = pickle.load(f)
Please first read the answer mentioned by zehnpaard to know the reason for the attribute error. Other than the solution he already provided, in python3
you can use the pickle.Unpickler
class and override the find_class
method as mentioned below:
import pickle
class CustomUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if name == 'Manager':
from settings import Manager
return Manager
return super().find_class(module, name)
pickle_data = CustomUnpickler(open('file_path.pkl', 'rb')).load()
If you're still getting this error even after importing the appropriate classes in the loading module (zehnpaard's solution #1), then the find_class
function of pickle.Unpickler
can be overwritten and explicitly directed to look in the current module's namespace.
import pickle
from settings import Manager
class CustomUnpickler(pickle.Unpickler):
def find_class(self, module, name):
try:
return super().find_class(__name__, name)
except AttributeError:
return super().find_class(module, name)
pickle_data = CustomUnpickler(open('file_path.pkl', 'rb')).load()
## No exception trying to get 'Manager'
Note: This method loses the relative-import path information stored in module
. So, be careful of namespace collisions in your pickled classes.