Python序列化与反序列化详解:pickle、json与shelve模块实战

陆景行
陆景行
发布时间:2026-10-13| 3人浏览过
原创

本文深入解析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序列化和反序列化就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。