Python序列化与反序列化详解:pickle、json与shelve模块实战
本文深入解析Python中常用的序列化工具,涵盖pickle、json及shelve模块。重点讲解如何在变量与文件中存储数据,以及如何处理自定义类的序列化与反序列化问题,包括使用default和object_hook参数解决JSON序列化报错,以及shelve模块的writeback机制,帮助开发者实现对象状态的持久化与恢复。
对象序列化可将对象状态保存至变量或文件中,从而延长其生命周期并支持后续读取。Python提供了多个模块来实现这一功能。
pickle模块
存储在变量中
dumps(obj)返回存入的字节
dic = {'age': 23, 'job': 'student'}
byte_data = pickle.dumps(dic)
# out -> b'\x80\x03}q\x00(X\x03\x00\x00\...'
print(byte_data)
读取数据
数据以字节形式保存在byte_data变量中,需要再次使用时调用loads函数即可恢复。
obj = pickle.loads(byte_data) print(obj)
存储在文件中
对象也可持久化存储到文件中,此时使用dump和load函数。需注意与dumps/loads的区别,这两个函数名末尾没有's'。由于pickle写入的是二进制数据,文件打开模式必须使用wb(写入二进制)和rb(读取二进制)。
# 序列化
with open('abc.pkl', 'wb') as f:
dic = {'age': 23, 'job': 'student'}
pickle.dump(dic, f)
# 反序列化
with open('abc.pkl', 'rb') as f:
aa = pickle.load(f)
print(aa)
print(type(aa)) # <class 'dict'>
序列化用户自定义对象
假设定义了一个名为Person的类。
class Person:
def __init__(self, name, age, job):
self.name = name
self.age = age
self.job = job
def work(self):
print(self.name, 'is working...')
pickle同样支持写入该类及其实例。不仅可以序列化类的实例,还可以直接序列化类本身。
# 将实例存储在变量中,当然也能存在文件中
a_person = Person('abc', 22, 'waiter')
person_abc = pickle.dumps(a_person)
p = pickle.loads(person_abc)
p.work()
# 将类本身存储在变量中,loads的时候返回类本身,而非它的一个实例
class_Person = pickle.dumps(Person)
Person = pickle.loads(class_Person)
p = Person('Bob', 23, 'Student')
p.work()
# 下面这个例子演示的就是将类存储在文件中
# 序列化
with open('person.pkl', 'wb') as f:
pickle.dump(Person, f)
# 反序列化
with open('person.pkl', 'rb') as f:
Person = pickle.load(f)
aa = Person('gg', 23, '6')
aa.work()
json模块
虽然pickle能序列化几乎所有Python对象,但json作为更标准的格式,具有更好的可读性(pickle为二进制数据)和跨平台兼容性,是另一种不错的选择。
json使用的四个函数名与pickle一致。
序列化为字符串
dic = {'age': 23, 'job': 'student'}
dic_str = json.dumps(dic)
print(type(dic_str), dic_str)
# out: <class 'str'> {"age": 23, "job": "student"}
dic_obj = json.loads(dic_str)
print(type(dic_obj), dic_obj)
# out: <class 'dict'> {'age': 23, 'job': 'student'}
可以看到,dumps函数将对象转换为字符串。loads函数则将其恢复为字典。
存储为json文件
同样可以将数据存储在json文件中。
dic = {'age': 23, 'job': 'student'}
with open('abc.json', 'w', encoding='utf-8') as f:
json.dump(dic, f)
with open('abc.json', encoding='utf-8') as f:
obj = json.load(f)
print(obj)
存储自定义对象
对于上述Person对象,如果直接序列化会报错。
aa = Person('Bob', 23, 'Student')
with open('abc.json', 'w', encoding='utf-8') as f:
json.dump(aa, f) # 报错
报错信息为Object of type 'Person' is not JSON serializable。此时在dump函数中传入default参数即可,该参数接收一个函数,用于将对象转换为字典。
编写一个转换函数即可。
def person2dict(person):
return {'name': person.name,
'age': person.age,
'job': person.job}
这样返回的就是一个字典。对象实例有一个方法可以简化这一过程,即直接调用实例的__dict__。例如:
print(aa.__dict) # {'name': 'Bob', 'age': 23, 'job': 'Student'}
这种方式非常方便。
同时在读取时,load出来的是一个字典,需要将其转回对象。这同样需要一个object_hook参数,该参数接收一个函数,用于将字典转换为对象。
def dict2person(dic): return Person(dic['name'], dic['age'], dic['job'])
于是完整的程序应该写成下面这样:
with open('abc.json', 'w', encoding='utf-8') as f:
json.dump(aa, f, default=person2dict)
with open('abc.json', encoding='utf-8') as f:
obj = json.load(f, object_hook=dict2person)
print(obj.name, obj.age, obj.job)
obj.work()
由于可以使用__dict__代替person2dict函数,再使用lambda函数简化。
with open('abc.json', 'w', encoding='utf-8') as f:
json.dump(aa, f, default=lambda obj: obj.__dict__)
以上是存储到文件的情况,存储到变量的操作类似。
不过就目前的了解,尚不清楚如何像pickle那样方便地将自定义类本身使用json序列化,或许需要借助其他扩展函数,后续遇到再行补充。
shelve模块
还有一个不太常用但实用的模块,通常使用open函数即可。shelve以键值对的形式存储数据。
with shelve.open('aa') as f:
f['person'] = {'age': 23, 'job': 'student'}
f['person']['age'] = 44 # 这里试图改变原来的年龄23
f['numbers'] = [i for i in range(10)]
with shelve.open('aa') as f:
person = f['person']
print(person) # {'age': 23, 'job': 'student'}
nums = f['numbers']
print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
文件不要有后缀名,在Windows下会生成aa.bak、aa.dat、aa.dir三个文件(数量较多)。其中bak和dir文件是可以查看的(似乎两个文件内容相同)。在下面这个例子中生成这样的数据。
'person', (0, 44) 'numbers', (512, 28)
允许写回--writeback
有个细节,当我们读取键person时,发现age仍然是23岁,执行f['person']['age'] = 44后并没有变成44。下面的写法:
with shelve.open('aa', writeback=True) as f:
dic = {'age': 23, 'job': 'student'}
f['person'] = dic
dic['age'] = 44
f['person'] = dic
相当于赋值了两次,这种方法是可以改变值的。
默认情况下,直接使用f['person']改变其中的值之后,不会更新已存储的值,即没有把更新写回到文件,即使文件被close后也是如此。如果有此需要,在open函数中添加一个参数writeback=True。再次运行下看看年龄就被改变了。
写入自定义对象
依然使用上面的Person对象。
with shelve.open('aa') as f:
f['class'] = Person
# 写入类本身
with shelve.open('aa') as f:
Person = f['class']
a = Person('Bob', 23, 'Student')
a.work()
上面的例子说明shelve也可以序列化类本身。当然序列化实例肯定是可以的。
with shelve.open('aa') as f:
a = Person('God', 100, 'watch')
f['class'] = a
with shelve.open('aa') as f:
god = f['class']
god.work()
注意,由于我们使用with open打开,故不用写close语句。此模块是有close函数的,如果不是with方法打开的一定要记得主动close。
以上这篇老生常谈Python序列化和反序列化就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









