无法使用 pickle 和多个模块加载文件
Posted
技术标签:
【中文标题】无法使用 pickle 和多个模块加载文件【英文标题】:Unable to load files using pickle and multiple modules 【发布时间】:2015-02-28 04:56:53 【问题描述】:我正在尝试创建一个使用设置和 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_())
每个用户都是一个类并被放入一个列表中,然后当我仅加载设置文件并验证登录时使用 pickle 保存列表一切正常但是当我打开 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)>
【问题讨论】:
我不知道这是否与您的问题有关,但是如果您使用with
打开文件,则不需要close
。当 with 块结束时,上下文管理器会自动为您关闭它。此外,您的 verifyUser
方法无法正常工作。它只会查看用户列表中的第一个用户。
感谢您的反馈!是的,我只对一位用户进行过尝试,我会重做,您知道为什么会出现该错误吗?
能否把user_data.pkl文件的内容贴出来,假设是现阶段的测试数据?
€•Ñ ]”Œ__main__”ŒManager”“”)”’””(Œlastname”ŒHammer”Œgender”ŒM”Œ_dob”JüxËŒ_hashkey”Œ@99b3bcf690e653a177c602dd9999093b9eb29e50a3af9a059af3fcbfab476a16”Œ _username”Œ30JaHa”Œtype”hŒ firstname”ŒJack”uba.
这是一个泡菜文件,所以是的,它看起来像什么,它是一个包含名字、姓氏、出生日期、用户名、哈希键(密码)和访问级别的对象
对,还有三个问题 - 您的用户对象是从名为 Manager 的类创建的吗?这是在设置模块中定义的吗?您实际上是如何创建 user_data.pkl 的?
【参考方案1】:
如果您使用莳萝转储/加载模型将起作用
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)
【讨论】:
【参考方案2】:如果在加载模块 (zehnpaard's solution #1) 中导入适当的类后仍然出现此错误,则可以覆盖 pickle.Unpickler
的 find_class
函数并显式指示在当前模块的命名空间中查找.
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'
注意:此方法会丢失存储在module
中的相对导入路径信息。因此,请注意腌制类中的命名空间冲突。
【讨论】:
【参考方案3】:如果您在模块外部定义了一个类,其对象在泡菜数据中, 你必须导入类
from outside_module import DefinedClass1, DefinedClass2, DefinedClass3
with open('pickle_file.pkl', 'rb') as f:
pickle_data = pickle.load(f)
【讨论】:
如果模块存在于不同的文件夹中,您可能需要为模块文件夹创建一个__init__.py
文件。见 (***.com/a/21995949/5909698)【参考方案4】:
请先阅读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()
【讨论】:
太棒了!我发现这个解决方案比 zehnpaard 接受的答案更强大,因为它允许在代码中而不是在生成的 pickle 中更改类(或函数)的位置。 对于任何想知道的人来说,这个解决方案确实扩展到多个导入,只需添加额外的 if 语句。 @thedeg123 是的,你也可以这样做【参考方案5】:问题是您通过实际运行“设置”模块来腌制设置中定义的对象,然后您试图从GUI
模块中取消腌制对象。
请记住,pickle 实际上并不存储有关如何构造类/对象的信息,并且在 unpickle 时需要访问该类。详情请见wiki on using Pickle。
在 pkl 数据中,您看到被引用的对象是 __main__.Manager
,因为当您创建 pickle 文件时,“设置”模块是 main(即您运行了“设置”模块作为调用addUser
函数的主脚本)。
然后,您尝试在“Gui”中取消腌制 - 使该模块具有名称 __main__
,并且您正在该模块中导入设置。当然,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)
解决办法是:
-
您可以通过显式导入使该类在***模块(即 GUI 或 main_module)的命名空间中可用,或者
您从与您将在其中打开它的***模块相同的***模块创建泡菜文件(即从 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)
【讨论】:
谢谢!!这真的很有帮助,该程序现在正在运行,您对导入用户类是正确的,这似乎比选项 2 更有效! 很高兴听到这个消息!我同意,在这个阶段,在 GUI 模块中显式导入每个类要简单得多。另一方面,一旦您完成了代码,我想您将使用 GUI 界面添加/删除用户 - 因此您的对象将使用 GUI 作为 main 模块进行腌制,并且类将是 Settings.User、Settings.Manager 等。 是的,因为这只是登录 gui,我想它现在很有效,顺便说一句,如果我还有其他问题?我应该再问一遍吗? 是的,如果这是一个单独的问题,那么最好创建另一个问题而不是继续添加这个问题。 如果您知道如何操作会有所帮助吗?同一个项目的一部分! ***.com/questions/27745094/…以上是关于无法使用 pickle 和多个模块加载文件的主要内容,如果未能解决你的问题,请参考以下文章
如何在小 GUI 中修复“allow_pickle=False 时无法加载对象数组”