Python数据持久化与序列化方法详解
在系统管理工作中,构建GUI应用程序虽并非传统职责,但它是一项极具价值的技能。有时,你可能需要为用户创建简单应用;有时则是给自己用;还有时,虽非必需,但它能让某些任务执行得更顺畅。当你熟练掌握构建GUI应用的技巧后,会惊讶地发现自己频繁用到这项能力。
而在数据处理方面,数据持久化是一个重要概念。简单来说,数据持久化是指将数据保存起来以供后续使用,即使保存数据的进程终止,数据依然存在。通常,这是通过将数据转换为某种格式并写入磁盘来实现的。数据格式有人类可读的,如XML或YAML;也有人类无法直接使用的,如Berkeley DB文件(bdb)或SQLite数据库。
1. 数据持久化的应用场景
以下是一些需要保存数据以供后续使用的场景:
-
文件修改跟踪脚本
:你有一个脚本用于跟踪目录中文件的最后修改日期,需要偶尔运行该脚本来查看自上次运行后哪些文件发生了变化。此时,文件的相关数据就需要保存下来,下次运行脚本时使用。
-
网络性能监测脚本
:有一台机器存在潜在网络问题,你决定每15分钟运行一次脚本,查看它与网络中其他多台机器的ping响应时间。这些ping时间数据可以保存到持久化数据文件中,以便后续分析。
2. 简单序列化方法
序列化是将数据存储到磁盘以供后续使用的过程,这里先介绍简单序列化,即保存数据到磁盘时不保存数据之间的关系。
2.1 Pickle模块
Pickle是Python标准库中的一个模块,是最基本的简单序列化机制。从农业或烹饪的角度理解,“pickling”就像是将食物保存起来放入罐中,日后再使用。在Python中,使用Pickle模块可以将对象写入磁盘,退出Python进程,之后再重新启动进程,从磁盘读取对象并与之交互。
可Pickle的对象类型
:
- None、true和false
- 整数、长整数、浮点数、复数
- 普通字符串和Unicode字符串
- 仅包含可Pickle对象的元组、列表、集合和字典
- 在模块顶层定义的函数
- 在模块顶层定义的内置函数
- 在模块顶层定义的类
- 其
__dict__
或
__setstate__()
可Pickle的此类类的实例
序列化对象到磁盘 :
In [1]: import pickle
In [2]: some_dict = {'a': 1, 'b': 2}
In [3]: pickle_file = open('some_dict.pkl', 'w')
In [4]: pickle.dump(some_dict, pickle_file)
In [5]: pickle_file.close()
反序列化Pickle文件 :
In [1]: import pickle
In [2]: pickle_file = open('some_dict.pkl', 'r')
In [3]: another_name_for_some_dict = pickle.load(pickle_file)
In [4]: another_name_for_some_dict
Out[4]: {'a': 1, 'b': 2}
可以将多个对象存储到一个Pickle文件中:
In [1]: list_of_dicts = [{str(i): i} for i in range(5)]
In [2]: list_of_dicts
Out[2]: [{'0': 0}, {'1': 1}, {'2': 2}, {'3': 3}, {'4': 4}]
In [3]: import pickle
In [4]: pickle_file = open('list_of_dicts.pkl', 'w')
In [5]: for d in list_of_dicts:
...: pickle.dump(d, pickle_file)
...:
...:
In [6]: pickle_file.close()
反序列化包含多个对象的Pickle文件:
In [1]: import pickle
In [2]: pickle_file = open('list_of_dicts.pkl', 'r')
In [3]: while 1:
...: try:
...: print pickle.load(pickle_file)
...: except EOFError:
...: print "EOF Error"
...: break
...:
...:
{'0': 0}
{'1': 1}
{'2': 2}
{'3': 3}
{'4': 4}
EOF Error
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)
将自定义类的对象进行Pickle序列化:
#!/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()
反序列化自定义类的Pickle文件:
#!/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默认使用一种接近人类可读的协议进行序列化,也可以选择二进制协议。二进制协议在处理对象序列化时间较长时可能更合适:
In [1]: import pickle
In [2]: default_pickle_file = open('default.pkl', 'w')
In [3]: binary_pickle_file = open('binary.pkl', 'wb')
In [4]: d = {'a': 1}
In [5]: pickle.dump(d, default_pickle_file)
In [6]: pickle.dump(d, binary_pickle_file, -1)
In [7]: default_pickle_file.close()
In [8]: binary_pickle_file.close()
2.2 cPickle模块
在Python标准库中,还有一个
cPickle
模块,它是用C语言实现的。如果发现对象的Pickle序列化过程耗时较长,可以考虑使用
cPickle
模块,其语法与
pickle
模块相同。
2.3 shelve模块
shelve
模块提供了一个简单易用的对象持久化接口,简化了多个对象的持久化操作。可以将多个对象存储在同一个持久化对象存储中,并轻松取回。使用
shelve
存储对象类似于使用Python字典。
示例代码 :
In [1]: import shelve
In [2]: d = shelve.open('example.s')
In [3]: d
Out[3]: {}
In [4]: d['key'] = 'some value'
In [5]: d.close()
In [6]: d2 = shelve.open('example.s')
In [7]: d2
Out[7]: {'key': 'some value'}
使用
shelve
时需要注意两个问题:
-
关闭对象
:操作完成后必须调用
close()
方法,否则对
shelve
对象所做的更改将不会被持久化。
# 未关闭对象导致数据丢失示例
In [1]: import shelve
In [2]: d = shelve.open('lossy.s')
In [3]: d['key'] = 'this is a key that will persist'
In [4]: d
Out[4]: {'key': 'this is a key that will persist'}
In [5]: d.close()
# 再次打开,添加新项但不关闭
In [1]: import shelve
In [2]: d = shelve.open('lossy.s')
In [3]: d
Out[3]: {'key': 'this is a key that will persist'}
In [4]: d['another_key'] = 'this is an entry that will not persist'
# 直接退出,未调用d.close()
# 再次打开查看
In [1]: import shelve
In [2]: d = shelve.open('lossy.s')
In [3]: d
Out[3]: {'key': 'this is a key that will persist'}
- 可变对象的修改 :默认情况下,对持久化对象的内联更改不会被捕获。可以通过两种方法解决:
-
特定针对性方法
:重新赋值给
shelve对象。
In [1]: import shelve
In [2]: d = shelve.open('mutable_nonlossy.s')
In [3]: d['key'] = []
In [4]: temp_list = d['key']
In [5]: temp_list.append(1)
In [6]: d['key'] = temp_list
In [7]: d.close()
-
通用的全面方法
:设置
writeback标志为True。但这样会将访问的对象缓存到内存中,在调用close()时再进行持久化,可能会增加内存使用和文件同步时间。
In [1]: import shelve
In [2]: d = shelve.open('mutable_nonlossy.s', writeback=True)
In [3]: d['key'] = []
In [4]: d['key'].append(1)
In [5]: d.close()
3. YAML模块
YAML全称可能是“YAML ain’t markup language”或“yet another markup language”,是一种常用于以纯文本布局存储、检索和更新数据的格式,数据通常具有层次结构。
在Python中使用YAML,可先通过
easy_install PyYAML
进行安装。选择YAML而非内置的
pickle
有两个吸引人的原因:
-
人类可读
:语法类似于配置文件,在编辑配置文件是个不错选择的场景下,YAML可能是个好的选择。
-
多语言支持
:YAML解析器已在许多其他语言中实现,如果你需要在Python应用程序和其他语言编写的应用程序之间传输数据,YAML可以作为一个很好的中间解决方案。
序列化示例 :
In [1]: import yaml
In [2]: yaml_file = open('test.yaml', 'w')
In [3]: d = {'foo': 'a', 'bar': 'b', 'bam': [1, 2,3]}
In [4]: yaml.dump(d, yaml_file, default_flow_style=False)
In [5]: yaml_file.close()
生成的YAML文件内容如下:
bam:
- 1
- 2
- 3
bar: b
foo: a
反序列化示例 :
In [1]: import yaml
In [2]: yaml_file = open('test.yaml', 'r')
In [3]: yaml.load(yaml_file)
Out[3]: {'bam': [1, 2, 3], 'bar': 'b', 'foo': 'a'}
YAML还支持非块模式的序列化:
In [1]: import yaml
In [2]: yaml_file = open('nonblock.yaml', 'w')
In [3]: d = {'foo': 'a', 'bar': 'b', 'bam': [1, 2,3]}
In [4]: yaml.dump(d, yaml_file)
In [5]: yaml_file.close()
生成的非块模式YAML文件内容如下:
bam: [1, 2, 3] bar: b foo: a
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()
总结
本文详细介绍了Python中数据持久化和简单序列化的多种方法,包括
pickle
、
cPickle
、
shelve
和
YAML
。每种方法都有其特点和适用场景,
pickle
是基本的序列化模块,
cPickle
性能更优,
shelve
简化了多对象持久化,
YAML
具有人类可读和多语言支持的优势。在实际应用中,需要根据具体需求选择合适的方法。
以下是这些序列化方法的比较表格:
| 方法 | 特点 | 适用场景 |
| ---- | ---- | ---- |
| pickle | 基本的序列化模块,支持自定义类对象,默认协议接近人类可读 | 简单对象的序列化和反序列化,对性能要求不高 |
| cPickle | 用C语言实现,性能优于pickle | 对象序列化耗时较长的情况 |
| shelve | 提供简单易用的多对象持久化接口,类似字典操作 | 需要存储多个对象并方便取回的场景 |
| YAML | 人类可读,多语言支持,数据通常具有层次结构 | 需要在不同语言应用程序间传输数据,或数据需要人工编辑的场景 |
通过合理运用这些方法,可以更好地实现数据的持久化存储和管理。
以下是使用
pickle
模块进行对象序列化和反序列化的流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(导入pickle模块):::process
B --> C(创建要序列化的对象):::process
C --> D(打开文件以写入模式):::process
D --> E(使用pickle.dump()序列化对象到文件):::process
E --> F(关闭文件):::process
F --> G{是否需要反序列化?}:::decision
G -->|是| H(打开文件以读取模式):::process
H --> I(使用pickle.load()从文件中反序列化对象):::process
I --> J(使用反序列化后的对象):::process
J --> K([结束]):::startend
G -->|否| K([结束]):::startend
这个流程图展示了使用
pickle
模块进行对象序列化和反序列化的基本流程。首先,导入
pickle
模块,创建要序列化的对象,然后将对象写入文件。接着,根据是否需要反序列化,决定是否从文件中读取对象。如果需要反序列化,将读取文件并使用反序列化后的对象;如果不需要,则直接结束流程。这个流程清晰地展示了
pickle
模块的使用步骤。
Python数据持久化与序列化方法详解
在之前的内容中,我们已经对Python数据持久化的多种简单序列化方法进行了详细介绍。接下来,我们将进一步探讨这些方法在实际应用中的注意事项和一些拓展内容,以及对不同序列化方法的性能对比分析。
4. 序列化方法实际应用注意事项
4.1 Pickle模块的安全性问题
虽然
pickle
模块非常方便,但在使用时需要注意安全性问题。由于
pickle
可以反序列化任意对象,当从不可信的源接收
pickle
数据时,可能会执行恶意代码。例如,如果一个攻击者能够控制
pickle
文件的内容,他们可以构造一个恶意对象,在反序列化时执行任意代码。因此,在实际应用中,不要从不可信的源加载
pickle
数据。
4.2 shelve模块的文件锁定问题
shelve
模块在多进程或多线程环境下使用时,可能会出现文件锁定问题。当多个进程或线程同时尝试访问和修改同一个
shelve
文件时,可能会导致数据不一致或文件损坏。为了避免这个问题,可以使用锁机制来确保同一时间只有一个进程或线程可以访问
shelve
文件。
import shelve
import threading
# 创建一个锁对象
lock = threading.Lock()
def update_shelve():
with lock:
d = shelve.open('example.s')
try:
if 'counter' not in d:
d['counter'] = 0
d['counter'] += 1
finally:
d.close()
# 创建多个线程来更新shelve文件
threads = []
for _ in range(10):
t = threading.Thread(target=update_shelve)
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
# 查看最终结果
d = shelve.open('example.s')
print(d['counter'])
d.close()
4.3 YAML模块的性能问题
虽然YAML具有人类可读和多语言支持的优点,但在处理大量数据或对性能要求较高的场景下,YAML的序列化和反序列化速度可能较慢。这是因为YAML需要解析和生成文本格式的数据,相比二进制格式的序列化方法,性能会有所损失。在这种情况下,可以考虑使用其他性能更高的序列化方法,如
cPickle
。
5. 序列化方法性能对比分析
为了更直观地了解不同序列化方法的性能差异,我们可以进行一个简单的性能测试。以下是一个测试代码示例,用于比较
pickle
、
cPickle
和
YAML
在序列化和反序列化一个大型字典时的性能。
import pickle
import cPickle
import yaml
import time
# 创建一个大型字典
large_dict = {str(i): i for i in range(10000)}
# 测试pickle的性能
start_time = time.time()
pickle_file = open('pickle_test.pkl', 'w')
pickle.dump(large_dict, pickle_file)
pickle_file.close()
pickle_file = open('pickle_test.pkl', 'r')
loaded_dict_pickle = pickle.load(pickle_file)
pickle_file.close()
pickle_time = time.time() - start_time
# 测试cPickle的性能
start_time = time.time()
cPickle_file = open('cPickle_test.pkl', 'wb')
cPickle.dump(large_dict, cPickle_file)
cPickle_file.close()
cPickle_file = open('cPickle_test.pkl', 'rb')
loaded_dict_cPickle = cPickle.load(cPickle_file)
cPickle_file.close()
cPickle_time = time.time() - start_time
# 测试YAML的性能
start_time = time.time()
yaml_file = open('yaml_test.yaml', 'w')
yaml.dump(large_dict, yaml_file)
yaml_file.close()
yaml_file = open('yaml_test.yaml', 'r')
loaded_dict_yaml = yaml.load(yaml_file)
yaml_file.close()
yaml_time = time.time() - start_time
# 输出性能测试结果
print(f"Pickle序列化和反序列化时间: {pickle_time} 秒")
print(f"cPickle序列化和反序列化时间: {cPickle_time} 秒")
print(f"YAML序列化和反序列化时间: {yaml_time} 秒")
以下是根据上述测试代码可能得到的性能对比表格:
| 方法 | 序列化和反序列化时间(秒) |
| ---- | ---- |
| pickle | 0.123 |
| cPickle | 0.034 |
| YAML | 0.567 |
从表格中可以看出,
cPickle
的性能明显优于
pickle
和
YAML
。这是因为
cPickle
是用C语言实现的,执行速度更快。而
YAML
由于需要处理文本格式的数据,性能相对较差。
6. 序列化方法的选择建议
根据不同的应用场景和需求,可以按照以下建议选择合适的序列化方法:
-
简单对象且对性能要求不高
:可以使用
pickle
模块,它是Python标准库中的基本序列化方法,支持自定义类对象,使用方便。
-
对性能要求较高
:优先选择
cPickle
模块,它的性能明显优于
pickle
,尤其在处理大量数据时更加显著。
-
需要存储多个对象并方便取回
:使用
shelve
模块,它提供了类似字典的操作接口,简化了多对象的持久化操作。
-
需要在不同语言应用程序间传输数据或数据需要人工编辑
:选择
YAML
模块,它具有人类可读和多语言支持的优点。
总结
本文全面介绍了Python中数据持久化和简单序列化的多种方法,包括
pickle
、
cPickle
、
shelve
和
YAML
。详细阐述了它们的使用方法、特点、适用场景以及在实际应用中需要注意的问题。通过性能对比分析,我们可以更清楚地了解不同方法的性能差异,从而在实际开发中根据具体需求选择最合适的序列化方法。
以下是不同序列化方法选择的决策流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B{是否需要多语言支持?}:::decision
B -->|是| C(YAML):::process
B -->|否| D{是否对性能要求高?}:::decision
D -->|是| E(cPickle):::process
D -->|否| F{是否需要存储多个对象?}:::decision
F -->|是| G(shelve):::process
F -->|否| H(pickle):::process
C --> I([结束]):::startend
E --> I([结束]):::startend
G --> I([结束]):::startend
H --> I([结束]):::startend
这个流程图可以帮助我们在面对不同的需求时,快速选择合适的序列化方法。通过合理运用这些序列化方法,我们可以更好地实现数据的持久化存储和管理,提高程序的性能和可维护性。
超级会员免费看
1209

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



