利用pickle处理python对象
有这样一个需求:将通过Teacher类实例化的几个对象利用pickle模块dump进文件teacher_obj中,然后利用从其他地方取到的值去扩展这几个对象中某个对象的一个属性。
一、数据初始化如下:
1.1Teacher类及其对象实例化的实现
class Teacher: def __init__(self,name,school): self.name = name self.school = school self.classes = [] self.course = [] if __name__ == '__main__': # 实例化几个Teacher对象 teacher1 = Teacher('whw', 'HHHT') teacher2 = Teacher('wanghw', 'Baotou') teacher3 = Teacher('huozhiying', 'China')
我们可以看到,实例化的这三个Teacher类的对象中的classes属性为空列表:
1.2 pickle模块操作文件的类的实现
由于我们使用pickle模块时,dump、load与文件的修改操作都需要打开与关闭文件,于是我们将这些操作统统写到MyPickle类中去,这样可以大大的提高代码的可维护性:
import pickle import os #文件处理类 class MyPickle: def __init__(self,filename): self.filename = filename def dump(self,obj): with open(self.filename,'ab') as f: pickle.dump(obj,f) def loaditer(self):#利用迭代器......生成一个可迭代的对象 with open(self.filename,'rb') as f: while 1: try: obj = pickle.load(f) yield obj except: break def edit(self,obj): f_bak = MyPickle(self.filename+'.bak') with open(self.filename,'rb+') as f: for i in self.loaditer(): if i.name == obj.name: f_bak.dump(obj) else: f_bak.dump(i) os.replace(self.filename+'.bak',self.filename)
当然我们也可以利用这个类与文件teacher_obj的路径实例化出一个文件对象来:
#实例化一个MyPickle对象 mypickle = MyPickle('teacher_obj')
1.3将Teacher类实例化出的对象写进文件中:
利用Teacher类实例化出的三个对象以及MyPickle类实例化的出文件对象,我们可以将Teacher对象写进teacher_obj文件中去:
#将teacher对象写入文件中 mypickle.dump(teacher1) mypickle.dump(teacher2) mypickle.dump(teacher3)
我们可以验证一下teacher_obj文件中是否写进去了Teacher类实例化出的对象:
f_load1 = mypickle.loaditer() #f_load1是一个可迭代的对象 for i in f_load1: print(i)
具体的结果为:
我们可以看到:从teacher_obj读出来的文件内容确实是三个Teacher对象,而且,这里需要注意的一点是:MyPickle类中的loaditer()方法返回的是一个迭代器对象,利用这个迭代器对象(上图中的变量f_load1)我们可以遍历出文件中的所有对象。
二、数据初始化成功后,我们的需求来了:将生成的三个Teacher对象中name属性为'wanghw'的对象的classes属性的值(为一个列表)里面追加一个字符串‘HelloWorld’。
1.1 根据1.3中我们遍历的结果,我们第一时间会想到:通过遍历从文件中取到的迭代器对象(变量f_load1),当遍历到满足条件的对象时,我们再利用MyPickle类中的edit()方法“修改”一下就OK了!于是乎下面这段代码应运而生:
f_load1 = mypickle.loaditer() #f_load1是一个可迭代的对象 for i in f_load1: if i.name == 'whw':#实际中这个字符串可以是别的对象中取到的 i.classes.append('HelloWorld')#实际中这个字符串可以是别的对象中取到的 mypickle.edit(i) print('修改后的内容:%s %s' % (i.name, i.classes))
可是,当我们运行整个代码时,会发现这样的错误~~
这里需要稍微提一下edit()方法的“修改”过程:首先我们新建一个原文件名+'.bak'的新文件,将修改后的信息放入这个新文件中,最后利用os模块下的replace方法替换原来的文件,这样在我们用户看来就实现了“旧文件的内容替换为新内容”这样一个结果。
可是,我们再仔细研究,发现上报的错误是PermissionError,根据经验上报这个错误绝大多数的情况就是文件没有关闭造成的!但是,我们再看看MyPickle类的各个方法,明明都用的是with open方法呀,它不是可以自动为我们关闭文件的吗?
这里~~直接揭晓答案吧:
首先,with open方法确实会为我们自动关闭文件,但是请注意:它只会在文件操作完成后自动关闭!我们上面出现的错误是因为没有关闭文件就进行文件的replace操作。但是,究竟问题出现在了什么地方呢?
我们看下图的具体流程与解释:
其实,问题就是出现在了遍历迭代器对象f_load1里了,当我们找到需要处理的对象时,文件中的内容还没有全部迭代完毕,此时进行edit操作,肯定会上报PermissionError错误!
针对这种情况,我们把代码稍作修改:
f_load1 = mypickle.loaditer() #f_load1是一个可迭代的对象 global a for i in f_load1: if i.name == 'whw':#实际中这个字符串可以是别的对象中取到的 i.classes.append('HelloWorld')#实际中这个字符串可以是别的对象中取到的 a = i mypickle.edit(a) print('修改后的内容:%s %s'%(a.name,a.classes))
这里利用global定义一个变量a,当我们遍历到相应的对象时,先修改这个对象中的属性,再将修改后的对象传给a,接着继续遍历f_load1对象,全部完毕后a里面存的就是需要修改的那个对象,此时再进行edit操作,便可以得到我们想要的结果:
值得一提的是,根据“一切皆对象”的原则,在实际中我们可能会利用生成器去处理文件中的其他对象,如列表、字典甚至类等等,针对大家可能会用到的PermissionError这个错误我这里利用一个实例进行了简单的说明,希望对各位有所帮助。
最后,送上程序完整的代码(需要在程序的同目录下自己新建一个teacher_obj文件):
import pickle import os #文件处理类 class MyPickle: def __init__(self,filename): self.filename = filename def dump(self,obj): with open(self.filename,'ab') as f: pickle.dump(obj,f) def loaditer(self):#利用迭代器......生成一个可迭代的对象 with open(self.filename,'rb') as f: while 1: try: obj = pickle.load(f) yield obj except: break def edit(self,obj): f_bak = MyPickle(self.filename+'.bak') with open(self.filename,'rb+') as f: for i in self.loaditer(): if i.name == obj.name: f_bak.dump(obj) else: f_bak.dump(i) os.replace(self.filename+'.bak',self.filename) #Teacher类 class Teacher: def __init__(self,name,school): self.name = name self.school = school self.classes = [] self.course = [] if __name__ == '__main__': #实例化几个Teacher对象 teacher1 = Teacher('whw','HHHT') teacher2 = Teacher('wanghw','Baotou') teacher3 = Teacher('huozhiying','China') #实例化一个MyPickle对象 mypickle = MyPickle('teacher_obj') #将teacher对象写入文件中 mypickle.dump(teacher1) mypickle.dump(teacher2) mypickle.dump(teacher3) f_load1 = mypickle.loaditer() #f_load1是一个可迭代的对象 global a for i in f_load1: if i.name == 'whw':#实际中这个字符串可以是别的对象中取到的 i.classes.append('HelloWorld')#实际中这个字符串可以是别的对象中取到的 a = i mypickle.edit(a) print('修改后的内容:%s %s'%(a.name,a.classes))