Python 数据持久化与序列化方法详解
1. 引言
在系统管理工作中,构建 GUI 应用看似并非传统职责,但它可能是一项非常有价值的技能。有时你可能需要为用户或自己构建简单应用,即便当下认为不需要,它也可能让某些任务执行得更顺畅。而数据持久化则是将数据保存以便后续使用的重要技术,它确保数据在保存进程终止后仍然存在,通常通过将数据转换为特定格式并写入磁盘来实现。下面将详细介绍几种常见的简单序列化方法。
2. 数据持久化概述
数据持久化是指保存数据以供后续使用,这意味着即使保存数据的进程终止,数据也能继续存在。常见的做法是将数据转换为特定格式,然后写入磁盘。数据格式有人类可读的,如 XML 或 YAML;也有人类无法直接使用的,如 Berkeley DB 文件(bdb)或 SQLite 数据库。
以下是一些需要保存数据供后续使用的场景示例:
- 有一个脚本用于跟踪目录中文件的最后修改日期,你需要定期运行该脚本来查看自上次运行以来哪些文件发生了变化。此时,文件的相关数据就需要保存下来,以便下次运行脚本时使用。
- 有一台机器可能存在网络问题,你决定每 15 分钟运行一个脚本,查看它与网络中其他多台机器的 ping 响应时间。这些 ping 时间可以存储在持久数据文件中,以便后续分析。
3. 简单序列化方法
简单序列化是指将数据保存到磁盘,而不保存数据各部分之间的关系。以下介绍几种常见的简单序列化方法。
3.1 Pickle 模块
Pickle 是 Python 标准库中的一个模块,是最基本的“简单序列化”机制。从烹饪角度理解,就像把食物保存到罐子里,以后再使用。使用 Pickle 模块时,你可以将对象写入磁盘,退出 Python 进程,之后再重新启动进程,从磁盘读取对象并与之交互。
可序列化的对象类型
:
- None、True 和 False
- 整数、长整数、浮点数、复数
- 普通字符串和 Unicode 字符串
- 仅包含可 pickle 对象的元组、列表、集合和字典
- 在模块顶层定义的函数
- 在模块顶层定义的内置函数
- 在模块顶层定义的类
- 此类类的实例,其
__dict__
或
__setstate__()
是可 pickle 的
序列化对象到磁盘的示例代码 :
import pickle
some_dict = {'a': 1, 'b': 2}
pickle_file = open('some_dict.pkl', 'w')
pickle.dump(some_dict, pickle_file)
pickle_file.close()
反序列化 pickle 文件的示例代码 :
import pickle
pickle_file = open('some_dict.pkl', 'r')
another_name_for_some_dict = pickle.load(pickle_file)
print(another_name_for_some_dict)
pickle_file.close()
可以将多个对象写入单个 pickle 文件,示例代码如下:
list_of_dicts = [{str(i): i} for i in range(5)]
import pickle
pickle_file = open('list_of_dicts.pkl', 'w')
for d in list_of_dicts:
pickle.dump(d, pickle_file)
pickle_file.close()
从包含多个对象的 pickle 文件中反序列化并打印对象的示例代码:
import pickle
pickle_file = open('list_of_dicts.pkl', 'r')
while 1:
try:
print(pickle.load(pickle_file))
except EOFError:
print("EOF Error")
break
Pickle 还支持自定义类的序列化和反序列化。以下是一个自定义类的示例:
#!/usr/bin/env python
class MyClass(object):
def __init__(self):
self.data = []
def __str__(self):
return "Custom Class MyClass Data:: %s" % str(self.data)
def add_item(self, item):
self.data.append(item)
序列化自定义对象的示例代码:
#!/usr/bin/env python
import pickle
import custom_class
my_obj = custom_class.MyClass()
my_obj.add_item(1)
my_obj.add_item(2)
my_obj.add_item(3)
pickle_file = open('custom_class.pkl', 'w')
pickle.dump(my_obj, pickle_file)
pickle_file.close()
反序列化自定义对象的示例代码:
#!/usr/bin/env python
import pickle
import custom_class
pickle_file = open('custom_class.pkl', 'r')
my_obj = pickle.load(pickle_file)
print(my_obj)
pickle_file.close()
需要注意的是,反序列化代码不一定要显式导入要反序列化的自定义类,但必须能够找到该自定义类所在的模块。
Pickle 有默认协议和二进制协议两种选择。默认协议使用几乎人类可读的格式,而二进制协议可能在处理对象序列化时间较长时更适用。以下是使用两种协议的对比示例:
import pickle
default_pickle_file = open('default.pkl', 'w')
binary_pickle_file = open('binary.pkl', 'wb')
d = {'a': 1}
pickle.dump(d, default_pickle_file)
pickle.dump(d, binary_pickle_file, -1)
default_pickle_file.close()
binary_pickle_file.close()
3.2 cPickle 模块
在 Python 标准库中,还有一个 cPickle 模块,它是用 C 语言实现的。如果发现使用 pickle 模块序列化对象花费的时间较长,可以考虑使用 cPickle 模块,其语法与普通的 pickle 模块相同。
3.3 Shelve 模块
Shelve 模块提供了一个简单易用的对象持久化接口,简化了多个对象的持久化操作。它允许将多个对象存储在同一个持久对象存储中,并方便地取回。使用 Shelve 存储对象类似于使用 Python 字典。
以下是使用 Shelve 模块的示例代码:
import shelve
d = shelve.open('example.s')
d['key'] = 'some value'
d.close()
d2 = shelve.open('example.s')
print(d2)
d2.close()
使用 Shelve 模块有两个需要注意的地方:
-
关闭 Shelve 对象
:使用完 Shelve 对象后,必须调用
close()
方法,否则对对象所做的任何更改都不会被持久化。例如:
import shelve
d = shelve.open('lossy.s')
d['key'] = 'this is a key that will persist'
d.close()
# 再次打开,添加新项但不关闭
d = shelve.open('lossy.s')
d['another_key'] = 'this is an entry that will not persist'
# 未调用 d.close()
# 再次打开查看
d = shelve.open('lossy.s')
print(d)
d.close()
- 处理可变对象 :默认情况下,对持久对象的内联更改不会被捕获。可以通过以下两种方法解决:
- 特定针对性方法 :重新赋值给 Shelve 对象。示例代码如下:
import shelve
d = shelve.open('mutable_nonlossy.s')
d['key'] = []
temp_list = d['key']
temp_list.append(1)
d['key'] = temp_list
d.close()
-
全局通用方法
:更改 Shelve 对象的
writeback标志。如果将writeback标志设置为True,则 Shelve 对象中被访问的任何条目都会被缓存在内存中,当调用close()方法时,这些条目会被持久化。但这样做会有一定的代价,因为所有被访问的对象都会被缓存并在关闭时持久化,无论是否发生了更改,这会导致内存使用和文件同步时间随着访问的对象数量增加而增加。示例代码如下:
import shelve
d = shelve.open('mutable_nonlossy.s', writeback=True)
d['key'] = []
d['key'].append(1)
d.close()
4. YAML 格式
YAML 代表“YAML ain’t markup language”或“yet another markup language”,是一种常用于以纯文本布局存储、检索和更新数据的格式,数据通常具有层次结构。
在 Python 中开始使用 YAML 最简便的方法是使用
easy_install PyYAML
进行安装。选择 YAML 而不是内置的 pickle 有两个吸引人的原因:
-
人类可读
:YAML 的语法类似于配置文件,在需要编辑配置文件的场景中,YAML 是一个不错的选择。
-
多语言支持
:YAML 解析器已在多种其他语言中实现,如果需要在 Python 应用程序和其他语言编写的应用程序之间传输数据,YAML 可以作为一个很好的中间解决方案。
以下是使用 YAML 序列化和反序列化数据的示例:
序列化示例 :
import yaml
yaml_file = open('test.yaml', 'w')
d = {'foo': 'a', 'bar': 'b', 'bam': [1, 2, 3]}
yaml.dump(d, yaml_file, default_flow_style=False)
yaml_file.close()
反序列化示例 :
import yaml
yaml_file = open('test.yaml', 'r')
data = yaml.load(yaml_file)
print(data)
yaml_file.close()
YAML 还支持块样式和非块样式的序列化,以下是不同数据结构在两种样式下的序列化对比示例:
import yaml
d = {'first': {'second': {'third': {'fourth': 'a'}}}}
print(yaml.dump(d, default_flow_style=False))
print(yaml.dump(d))
d2 = [{'a': 'a'}, {'b': 'b'}, {'c': 'c'}]
print(yaml.dump(d2, default_flow_style=False))
print(yaml.dump(d2))
d3 = [{'a': 'a'}, {'b': 'b'}, {'c': [1, 2, 3, 4, 5]}]
print(yaml.dump(d3, default_flow_style=False))
print(yaml.dump(d3))
YAML 对于自定义类的处理与 pickle 类似。以下是序列化和反序列化自定义类对象的示例:
序列化自定义类对象 :
#!/usr/bin/env python
import yaml
import custom_class
my_obj = custom_class.MyClass()
my_obj.add_item(1)
my_obj.add_item(2)
my_obj.add_item(3)
yaml_file = open('custom_class.yaml', 'w')
yaml.dump(my_obj, yaml_file)
yaml_file.close()
反序列化自定义类对象 :
#!/usr/bin/env python
import yaml
import custom_class
yaml_file = open('custom_class.yaml', 'r')
my_obj = yaml.load(yaml_file)
print(my_obj)
yaml_file.close()
5. 总结
本文介绍了 Python 中几种常见的简单序列化方法,包括 Pickle、cPickle、Shelve 和 YAML。每种方法都有其特点和适用场景,你可以根据具体需求选择合适的方法来实现数据的持久化。
以下是几种方法的对比表格:
| 方法 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| Pickle | 内置模块,使用方便,可序列化多种对象类型 | 数据格式不直观,安全性较低 | 本地数据存储和恢复,对象类型复杂 |
| cPickle | 用 C 语言实现,速度快 | 与 Pickle 类似,安全性较低 | 对序列化速度要求较高的场景 |
| Shelve | 操作简单,类似字典使用 | 需要注意关闭对象和处理可变对象 | 简单的对象持久化,需要存储多个对象 |
| YAML | 人类可读,多语言支持 | 需要安装第三方库 | 需要与其他语言交互,数据需要人类可读 |
以下是简单序列化流程的 mermaid 流程图:
graph LR
A[开始] --> B{选择序列化方法}
B --> |Pickle| C[创建对象]
B --> |cPickle| C
B --> |Shelve| D[打开 Shelve 文件]
B --> |YAML| E[安装 PyYAML]
C --> F[序列化对象到文件]
D --> G[存储对象到 Shelve]
E --> H[创建数据结构]
H --> I[序列化数据到 YAML 文件]
F --> J[关闭文件]
G --> J
I --> J
J --> K[结束]
通过合理运用这些序列化方法,你可以更好地管理和保存数据,提高程序的效率和可维护性。
Python 数据持久化与序列化方法详解
6. 不同序列化方法的性能分析
在实际应用中,不同的序列化方法在性能上可能会有较大差异。了解这些差异有助于我们在不同场景下做出更合适的选择。
6.1 序列化时间对比
为了对比 Pickle、cPickle、Shelve 和 YAML 的序列化时间,我们可以编写一个简单的测试代码。以下是一个示例,用于序列化一个包含大量数据的字典:
import pickle
import cPickle
import shelve
import yaml
import time
# 生成一个包含大量数据的字典
data = {str(i): i for i in range(10000)}
# 测试 Pickle 序列化时间
start_time = time.time()
pickle_file = open('pickle_test.pkl', 'w')
pickle.dump(data, pickle_file)
pickle_file.close()
pickle_time = time.time() - start_time
# 测试 cPickle 序列化时间
start_time = time.time()
cPickle_file = open('cPickle_test.pkl', 'w')
cPickle.dump(data, cPickle_file)
cPickle_file.close()
cPickle_time = time.time() - start_time
# 测试 Shelve 序列化时间
start_time = time.time()
shelve_file = shelve.open('shelve_test.s')
shelve_file['data'] = data
shelve_file.close()
shelve_time = time.time() - start_time
# 测试 YAML 序列化时间
start_time = time.time()
yaml_file = open('yaml_test.yaml', 'w')
yaml.dump(data, yaml_file, default_flow_style=False)
yaml_file.close()
yaml_time = time.time() - start_time
print(f"Pickle 序列化时间: {pickle_time} 秒")
print(f"cPickle 序列化时间: {cPickle_time} 秒")
print(f"Shelve 序列化时间: {shelve_time} 秒")
print(f"YAML 序列化时间: {yaml_time} 秒")
一般来说,cPickle 由于是用 C 语言实现的,序列化速度通常会比 Pickle 快很多。而 YAML 由于要考虑人类可读性和多语言兼容性,序列化时间可能会相对较长。Shelve 的性能则取决于具体的操作和数据量。
6.2 内存占用对比
不同的序列化方法在内存占用上也可能有所不同。例如,当使用 Shelve 的
writeback
标志设置为
True
时,会缓存被访问的对象,这可能会导致内存占用增加。以下是一个简单的示例,展示 Shelve 在不同
writeback
设置下的内存占用情况:
import shelve
import psutil
import os
# 生成一个包含大量数据的字典
data = {str(i): i for i in range(10000)}
# 不使用 writeback
shelve_file = shelve.open('shelve_no_writeback.s')
shelve_file['data'] = data
process = psutil.Process(os.getpid())
memory_usage_no_writeback = process.memory_info().rss
shelve_file.close()
# 使用 writeback
shelve_file = shelve.open('shelve_writeback.s', writeback=True)
shelve_file['data'] = data
process = psutil.Process(os.getpid())
memory_usage_writeback = process.memory_info().rss
shelve_file.close()
print(f"不使用 writeback 时内存占用: {memory_usage_no_writeback} 字节")
print(f"使用 writeback 时内存占用: {memory_usage_writeback} 字节")
通过这个示例,我们可以看到使用
writeback
可能会导致内存占用显著增加,因此在处理大量数据时需要谨慎使用。
7. 序列化方法的安全性考虑
在使用序列化方法时,安全性也是一个重要的考虑因素。不同的序列化方法在安全性上可能存在差异。
7.1 Pickle 和 cPickle 的安全性问题
Pickle 和 cPickle 在反序列化时可能会执行任意代码,这意味着如果反序列化的数据来自不可信的源,可能会导致安全漏洞。例如,攻击者可以构造恶意的 pickle 数据,在反序列化时执行系统命令。以下是一个简单的示例,展示恶意 pickle 数据的潜在风险:
import pickle
import os
# 恶意代码
class MaliciousClass(object):
def __reduce__(self):
return (os.system, ('ls',))
# 序列化恶意对象
malicious_obj = MaliciousClass()
pickle_file = open('malicious.pkl', 'w')
pickle.dump(malicious_obj, pickle_file)
pickle_file.close()
# 反序列化恶意对象
pickle_file = open('malicious.pkl', 'r')
try:
pickle.load(pickle_file)
except Exception as e:
print(f"反序列化时出错: {e}")
finally:
pickle_file.close()
为了避免这种安全风险,在使用 Pickle 和 cPickle 时,应确保反序列化的数据来自可信的源。
7.2 YAML 的安全性问题
YAML 也存在类似的安全问题,特别是当使用某些高级特性时。例如,YAML 支持自定义构造函数,攻击者可以构造恶意的 YAML 数据,在反序列化时执行任意代码。为了提高安全性,建议使用安全加载模式。以下是一个使用安全加载模式的示例:
import yaml
yaml_data = """
!python/object/apply:os.system ["ls"]
"""
try:
data = yaml.safe_load(yaml_data)
print(data)
except yaml.constructor.ConstructorError:
print("检测到不安全的 YAML 数据")
通过使用
safe_load
方法,可以避免执行恶意的 YAML 构造函数。
8. 实际应用案例
以下是几个不同序列化方法在实际应用中的案例,帮助我们更好地理解它们的适用场景。
8.1 使用 Pickle 进行机器学习模型持久化
在机器学习中,训练好的模型需要保存下来以便后续使用。Pickle 是一个常用的选择,因为它可以方便地序列化和反序列化复杂的对象。以下是一个简单的示例,展示如何使用 Pickle 保存和加载一个简单的线性回归模型:
import pickle
from sklearn.linear_model import LinearRegression
import numpy as np
# 生成一些示例数据
X = np.array([[1], [2], [3], [4], [5]])
y = np.array([2, 4, 6, 8, 10])
# 训练线性回归模型
model = LinearRegression()
model.fit(X, y)
# 保存模型到文件
pickle_file = open('model.pkl', 'w')
pickle.dump(model, pickle_file)
pickle_file.close()
# 加载模型
pickle_file = open('model.pkl', 'r')
loaded_model = pickle.load(pickle_file)
pickle_file.close()
# 使用加载的模型进行预测
new_X = np.array([[6]])
prediction = loaded_model.predict(new_X)
print(f"预测结果: {prediction}")
8.2 使用 YAML 进行配置文件管理
在开发应用程序时,经常需要使用配置文件来存储一些参数。YAML 由于其人类可读性和层次结构,非常适合用于配置文件管理。以下是一个简单的示例,展示如何使用 YAML 读取和写入配置文件:
import yaml
# 定义配置数据
config = {
"database": {
"host": "localhost",
"port": 5432,
"username": "user",
"password": "password"
},
"api": {
"url": "https://example.com/api",
"key": "1234567890"
}
}
# 保存配置到 YAML 文件
yaml_file = open('config.yaml', 'w')
yaml.dump(config, yaml_file, default_flow_style=False)
yaml_file.close()
# 从 YAML 文件读取配置
yaml_file = open('config.yaml', 'r')
loaded_config = yaml.load(yaml_file)
yaml_file.close()
print(f"数据库主机: {loaded_config['database']['host']}")
print(f"API URL: {loaded_config['api']['url']}")
9. 总结与建议
通过对 Python 中几种常见的简单序列化方法的详细介绍,我们了解了它们的特点、适用场景、性能和安全性。在选择序列化方法时,应根据具体需求进行综合考虑。
- 如果需要处理复杂的对象类型,且对序列化速度有一定要求,同时数据来源可信 ,可以选择 cPickle。
- 如果只是进行简单的本地数据存储和恢复,对数据格式的直观性要求不高 ,Pickle 是一个不错的选择。
- 如果需要存储多个对象,且操作简单方便 ,可以使用 Shelve 模块,但要注意关闭对象和处理可变对象的问题。
- 如果数据需要与其他语言交互,或者需要人类可读的格式 ,YAML 是一个很好的解决方案。
以下是选择序列化方法的决策树 mermaid 流程图:
graph TD
A[开始] --> B{数据是否需要与其他语言交互?}
B --> |是| C{数据是否需要人类可读?}
C --> |是| D[选择 YAML]
C --> |否| E{对序列化速度要求高吗?}
E --> |是| F[选择 cPickle]
E --> |否| G[选择 Pickle]
B --> |否| H{是否需要存储多个对象?}
H --> |是| I[选择 Shelve]
H --> |否| E
通过合理选择和使用序列化方法,我们可以更好地实现数据的持久化,提高程序的效率和可维护性。同时,要始终关注数据的安全性,避免潜在的安全风险。
超级会员免费看

被折叠的 条评论
为什么被折叠?



