82

I'm trying to create a user system, which uses a setting and Gui module, and when the GUI module requests for the file to load up using pickle, I keep getting an attribute error. this is from the settings module:

import pickle
import hashlib

class User(object):
    def __init__(self, fname, lname, dob, gender):
        self.firstname = fname
        self.lastname = lname
        self._dob = dob
        self.gender = gender
        self.type = 'General'
        self._username = ''
        self._hashkey = ''

    def Report(self):
        print("Full Name: {0} {1}\nDate of Birth: {2}\nGender: {3}\nAccess Level: {4}".format(self.firstname,self.lastname, self._dob, self.gender, self.type))
        print(self._username)

    def Genusername(self):
        self._username = str(str(self._dob)[:2] + self.firstname[:2] + self.lastname[:2])
        saveUsers(users)

    def Genhashkey(self, password):
        encoded = password.encode('utf-8','strict')
        return hashlib.sha256(encoded).hexdigest()

    def Verifypassword(self, password):
        if self._hashkey == self.Genhashkey(password):
            return True
        else:
            return False

class SAdmin(User):
    def __init__(self, fname, lname, dob, gender):
        super().__init__(fname, lname, dob, gender)
        self.type = 'Stock Admin'

class Manager(User):
    def __init__(self, fname, lname, dob, gender):
        super().__init__(fname, lname, dob, gender)
        self.type = 'Manager'

def saveUsers(users):
    with open('user_data.pkl', 'wb') as file:
        pickle.dump(users, file, -1) # PICKLE HIGHEST LEVEL PROTOCOL

def loadUsers(users):
    try:        
        with open('user_data.pkl', 'rb') as file:
            temp = pickle.load(file)
            for item in temp:
                users.append(item)
    except IOError:
        saveUsers([])

def userReport(users):
    for user in users:
        print(user.firstname, user.lastname)

def addUser(users):
    fname = input('What is your First Name?\n > ')
    lname = input('What is your Last Name?\n > ')
    dob = int(input('Please enter your date of birth in the following format, example 12211996\n> '))
    gender = input("What is your gender? 'M' or 'F'\n >")
    level = input("Enter the access level given to this user 'G', 'A', 'M'\n > ")
    password = input("Enter a password:\n > ")
    if level == 'G':
        usertype = User
    if level == 'A':
        usertype = SAdmin
    if level == 'M':
        usertype = Manager
    users.append(usertype(fname, lname, dob, gender))
    user = users[len(users)-1]
    user.Genusername()
    user._hashkey = user.Genhashkey(password)
    saveUsers(users)

def deleteUser(users):
    userReport(users)
    delete = input('Please type in the First Name of the user do you wish to delete:\n > ')
    for user in users:
        if user.firstname == delete:
            users.remove(user)
    saveUsers(users)

def changePass(users):
    userReport(users)
    change = input('Please type in the First Name of the user you wish to change the password for :\n > ')
    for user in users:
        if user.firstname == change:
            oldpass = input('Please type in your old password:\n > ')
            newpass = input('Please type in your new password:\n > ')
            if user.Verifypassword(oldpass):
                user._hashkey = user.Genhashkey(newpass)
                saveUsers(users)
            else:
                print('Your old password does not match!')

def verifyUser(username, password):
    for user in users:
        if user._username == username and user.Verifypassword(password):
            return True
        else:
            return False  

if __name__ == '__main__':
    users = []
    loadUsers(users)

and this is the GUI module:

from PyQt4 import QtGui, QtCore
import Settings

class loginWindow(QtGui.QDialog):    
    def __init__(self):
        super().__init__()        
        self.initUI()

    def initUI(self):
        self.lbl1 = QtGui.QLabel('Username')
        self.lbl2 = QtGui.QLabel('Password')
        self.username = QtGui.QLineEdit()
        self.password = QtGui.QLineEdit()

        self.okButton = QtGui.QPushButton("OK")
        self.okButton.clicked.connect(self.tryLogin)
        self.cancelButton = QtGui.QPushButton("Cancel")

        grid = QtGui.QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(self.lbl1, 1, 0)
        grid.addWidget(self.username, 1, 1)
        grid.addWidget(self.lbl2, 2, 0)
        grid.addWidget(self.password, 2, 1)
        grid.addWidget(self.okButton, 3, 1)
        grid.addWidget(self.cancelButton, 3, 0)

        self.setLayout(grid)

        self.setGeometry(300, 300, 2950, 150)
        self.setWindowTitle('Login')
        self.show()

    def tryLogin(self):
        print(self.username.text(), self.password.text())
        if Settings.verifyUser(self.username.text(),self.password.text()):
            print('it Woks')
        else:
            QtGui.QMessageBox.warning(
                self, 'Error', 'Incorrect Username or Password')

class Window(QtGui.QMainWindow):
    def __init__(self):
        super().__init__()        


if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    users = []
    Settings.loadUsers(users)
    if loginWindow().exec_() == QtGui.QDialog.Accepted:
        window = Window()
        window.show()
        sys.exit(app.exec_())

each user is a class and are put into a list and then the list is saved using pickle when I load up just the settings file and verify the login everything works fine but when I open up the GUI module and try to verify it doesn't let me, the error I'm getting:

Traceback (most recent call last):
  File "C:\Users`Program\LoginGUI.py", line 53, in <module>
    Settings.loadUsers(users)
  File "C:\Users\Program\Settings.py", line 51, in loadUsers
    temp = pickle.load(file)
AttributeError: Can't get attribute 'Manager' on <module '__main__' (built-in)>
Masoud Rahimi
  • 5,785
  • 15
  • 39
  • 67
Inthu
  • 1,009
  • 1
  • 8
  • 16
  • 1
    I don't know if this is related to your problem, but you don't need to `close` a file if you opened it using `with`. The context manager will close it for you automatically when the with block ends. Also, your `verifyUser` method doesn't work properly. It only ever looks at the first user in the user list. – Kevin Jan 01 '15 at 16:07
  • Thank you for the feedback!, and yeah I only tried it with one user, I will redo that do you know why I'm getting that error? – Inthu Jan 01 '15 at 16:10
  • Can you post the content of the user_data.pkl file, assuming it's test data at this stage? – zehnpaard Jan 01 '15 at 17:34
  • `€•Ñ ]”Œ__main__”ŒManager”“”)}”’”}”(Œlastname”ŒHammer”Œgender”ŒM”Œ_dob”JüxËŒ_hashkey”Œ@99b3bcf690e653a177c602dd9999093b9eb29e50a3af9a059af3fcbfab476a16”Œ _username”Œ30JaHa”Œtype”hŒ firstname”ŒJack”uba.`It's a pickle file so yeah hats what it looks like, its an object containing firstname,lastname, DOB, username, hashkey(password), and acceslevel – Inthu Jan 01 '15 at 17:40
  • right, three more questions - are your user objects created from a class called Manager? Is this defined in the Setting module? How did you actually create user_data.pkl? – zehnpaard Jan 01 '15 at 17:50
  • there is a main class called user then subclasses this one is from the manager subclass and yes the classes are all created in the settings module: `def saveUsers(users): with open('user_data.pkl', 'wb') as file: pickle.dump(users, file, -1)` and they are loaded by this function: `def loadUsers(users): try: with open('user_data.pkl', 'rb') as file: temp = pickle.load(file) for item in temp: users.append(item) except IOError: saveUsers([])` – Inthu Jan 01 '15 at 17:55
  • OK thanks, I think the relevant code isn't the details of your login window, but rather the class definitions for individual users. How big is your Settings module? Is it small enough to for you to edit the question and add the entire module instead of just the three functions and the __name__=='__main__' clause? – zehnpaard Jan 01 '15 at 18:02
  • okay added the full module there you go, it isnt that big tbh – Inthu Jan 01 '15 at 18:05

5 Answers5

121

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:

  1. You make the class available within the namespace of the top-level module (i.e. GUI or main_module) through an explicit import, or
  2. 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, or class_def.main from main_module). This means that the pkl file will save the objects as Settings.Manager or class_def.Foo, which can then be found in the GUI`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)
zehnpaard
  • 6,003
  • 2
  • 25
  • 40
  • 1
    Thank you!! That really helped, the program is working now, you were right about importing the user classes, that seems like a more efficient way than option 2! – Inthu Jan 01 '15 at 19:20
  • Glad to hear that! I agree, at this stage it's much simpler to import each class explicitly in the GUI module. On the other hand, once you're finished with your code, I imagine you will be adding/deleting users using the GUI interface - so your objects will be pickled with GUI as the __main__ module, and the classes will be Settings.User, Settings.Manager etc. – zehnpaard Jan 01 '15 at 19:27
  • Yeah, since this is just the login gui i guess its efficient right now, btw if i have another question? Should i ask again? – Inthu Jan 01 '15 at 19:36
  • Yes, if it's a separate issue, then it's better to create another question than keep adding to this one. – zehnpaard Jan 01 '15 at 19:40
  • would be helpful if you know how to do this? part of the same project! http://stackoverflow.com/questions/27745094/pyqt4-qsql-how-to-get-data-from-a-relational-database-and-display-them – Inthu Jan 02 '15 at 17:51
  • This was a very useful answer for me. I was getting this pickling error but only in pycharm debugger. The solution was to import the module (file.py) and the class (inside the module) right before calling main() and after the . That solved it. Thank you!. i.e. "if __name__ == '__main__': import server from server import track_server_process main()" – tobi delbruck Aug 11 '21 at 16:02
56

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()
Pankaj Saini
  • 752
  • 6
  • 7
  • 5
    Awesome! I find this solution to be more robust than zehnpaard's accepted answer, because it allows to change the location of the class (or function) *in code*, rather than in the generated pickle. – EliadL Aug 01 '19 at 15:36
  • 1
    To anyone wondering, this solution does extend to multiple imports, just add additional if statements. – thedeg123 Aug 10 '19 at 21:49
  • @thedeg123 yes, you can do that as well – Pankaj Saini Aug 12 '19 at 08:42
  • @PankajSaini You should add the multiple imports case as suggested by user:thedeg123. It will be helpful to other readers. This answer is robust indeed. It helps fix unpicking bugs in older repositories. I used this answer to get this popular repository running for our field. User and answer duly acknowledged https://github.com/caltech-netlab/acnportal-experiments/pull/11 – lifezbeautiful Oct 12 '22 at 09:38
6

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.

Tristan Brown
  • 561
  • 5
  • 10
4

If you have a class defined outside the module, whose object is in pickle data, you have to import the class

from outside_module import DefinedClass1, DefinedClass2, DefinedClass3 

with open('pickle_file.pkl', 'rb') as f:
    pickle_data = pickle.load(f)
Vinoj John Hosan
  • 6,448
  • 2
  • 41
  • 37
  • you may need to create a `__init__.py` file for the module folder, if the module exists in different folders. see (https://stackoverflow.com/a/21995949/5909698) – Chidi Jul 01 '21 at 09:26
3

if you use dill dump/load model will work

import dill
from sklearn.preprocessing import FunctionTransformer

sp_clf = FunctionTransformer(lambda X:X.astype('float').fillna(0).applymap(abs))

with open('temp.joblib','wb') as io:
    dill.dump(sp_clf,io)

with open('temp.joblib','rb') as io:
    dd=dill.load(io)
smart lph
  • 131
  • 3