web-dev-qa-db-ja.com

pickleと複数のモジュールを使用してファイルをロードできません

設定とGuiモジュールを使用するユーザーシステムを作成しようとしています。GUIモジュールがpickleを使用してファイルのロードを要求すると、属性エラーが表示されます。これは設定モジュールからのものです:

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)

これがGUIモジュールです:

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_())

各ユーザーはクラスであり、リストに入れられ、設定ファイルだけをロードしてログインを検証するときにピクルを使用してリストが保存されますが、GUIモジュールを開いて検証しようとするとうまくいかない私が取得しているエラー:

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)>
30
Inthuson

問題は、あなたが実際に 'Settings'モジュールを実行することで設定で定義されたオブジェクトをピクルスするであり、GUIモジュールからオブジェクトのピクルを外そうとしていることです。

Pickleは実際にはクラス/オブジェクトがどのように構築されているかについての情報を保存せず、ピクルを解くときにクラスにアクセスする必要があることに注意してください。詳細については、 Pickleの使用に関するwiki を参照してください。

Pklデータで、参照されているオブジェクトは__main__.Managerであることがわかります。これは、 'Settings'モジュールがmainであったため、pickleファイルを作成したときです(つまり、 'Settings'モジュールをaddUser関数を呼び出すメインスクリプト)。

次に、 'Gui'でunpickleを実行します-そのモジュールの名前は__main__になり、そのモジュール内にSettingをインポートします。そのため、もちろんManagerクラスは実際にはSettings.Managerになります。しかし、pklファイルはこれを認識せず、__main__内でManagerクラスを探し、存在しないためAttributeErrorをスローします(Settings.Managerはありますが、__main__.Managerはしません)。

以下に、デモ用の最小限のコードセットを示します。

class_def.pyモジュール:

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()

上記を実行して、ピクルスデータを生成します。 main_module.pyモジュール:

import pickle

import class_def

if __name__=='__main__':
    with open('test_data.pkl', 'rb') as f:
        users = pickle.load(f)

上記を実行してpickleファイルを開こうとすると、表示されていたエラーとほぼ同じエラーがスローされます。 (少し異なりますが、私はPython 2.7)を使用しているためだと推測しています

解決策は次のいずれかです。

  1. 明示的なインポートを介して、トップレベルモジュール(つまり、GUIまたはmain_module)のネームスペース内でクラスを使用可能にします。または
  2. Pickleファイルは、それを開くモジュールと同じ最上位モジュールから作成します(つまり、GUIからSettings.addUser、またはmain_moduleからclass_def.mainを呼び出します)。これは、pklファイルがオブジェクトをSettings.Managerまたはclass_def.Fooとして保存し、GUI`main_module`名前空間で見つけることができることを意味します。

オプション1の例:

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)

オプション2の例:

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)
52
zehnpaard

最初に zehnpaard で示されている回答を読んで、属性エラーの理由を確認してください。彼が既に提供したソリューション以外の、python3を使用できますpickle.Unpicklerクラスおよびfind_class以下のメソッド:

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()
17
Pankaj Saini

オブジェクトがピクルデータにあるモジュールの外部で定義されたクラスがある場合、そのクラスをインポートする必要があります

from outside_module import DefinedClass1, DefinedClass2, DefinedClass3 

with open('pickle_file.pkl', 'rb') as f:
    pickle_data = pickle.load(f)
0