Python持久化管理pickle模块 笔记

引用部分来自:http://blog.csdn.net/jq0123/article/details/4319589

http://www.cnblogs.com/cobbliu/archive/2012/09/04/2670178.html


Programming Python, 3rd Edition 翻译
最新版本见:http://wiki.woodpecker.org.cn/moin/PP3eD

什么是持久化

持久性就是指保持对象,甚至在多次执行同一程序之间也保持对象。

Pickle对象

pickle是为了序列化/反序列化一个对象的,可以把一个对象持久化存储
比如你有一个对象,想下次运行程序的时候直接用,可以直接用pickle打包存到硬盘上。或者你想把一个对象传给网络上的其他程序,可以用pickle打包,然后传过去,那边的python程序用pickle反序列化,就可以用了。
用法上,它主要有两个函数:load和dump,load是从序列化之后的数据中解出来,dump是把对象序列化。

Python系统的标准部件,pickle模块。它可以将几乎任意的Python内存对象,转换为单一线性的字符串格式,**使之适于无格式文件存储**,或在可靠来源之间跨越网络套接口传输等等,并可反向转换。这种从对象到字符串的转换通常被称为**序列化**(serialization):将内存中的任意数据结构映射为串行字符串形式。


对象的字符串表达由于其线性的格式,有时也被称为字节流。它包含了原始内存中对象的所有内容和引用结构。当对象后来从其字节串重建时,内存中新建的对象与原对象具有相同的结构和值,但位于不同的内存地址。该重建对象实际上是**原对象的复制**。



Pickle可用于几乎所有的Python数据类型:数字、列表、字典、类实例、嵌套结构,等等,因此**它是存储数据的通用方法**。因为pickle包含的是Python本地对象,所以几乎没有数据库的API;对象存储与处理及后来的提取用的都是通常的Python语法。

使用对象pickle

第一次听到pickle,可能觉得有点复杂,但好消息是,Python隐藏了所有从对象到字符串转换的复杂性。事实上,pickle模块的接口简单易用,简直令人难以置信。例如,要pickle对象到一个序列化字符串,我们可以生成一个pickler,并调用其方法,或使用模块中的便捷函数来达到相同的效果:

dump把pickler序列化

生成一个新的pickler,用来pickle到一个打开的输出文件对象file:

P = pickle.Pickler( file) 

写一个对象到pickler的文件/流:

P.dump( object) 

等同于上两个调用的组合:pickle对象到一个打开的文件:

pickle.dump( object, file) 

返回一个字符串作为已pickle对象的表达:

string = pickle.dumps( object) 

load将pickler反序列化

从一个序列化字符串unpickle回原始对象是类似的,可以用对象也可以用便捷函数接口:

生成一个unpickler,用来从一个打开的文件对象file unpickle:

U = pickle.Unpickler( file) 

从unpickler的文件/流读取一个对象:

object = U.load( )

等同于上两个调用的组合:从一个打开的文件unpickle一个对象:

object = pickle.load( file) 

从字符串读取一个对象,而不是从文件:

object = pickle.loads( string) 

Pickler和Unpickler是导出类。在上述所有情况下,file是个已打开的文件对象,或者是实现了以下文件对象属性的任何对象:

Pickler会调用文件的write方法,参数是个字符串。

Unpickler会调用文件的read方法,参数是字节数,以及readline,无参数。

任何提供这些属性的对象都可以作为file参数传入。特别是,file可以是一个提供了读/写方法的Python类实例(即预期的类似文件的接口)。这让您可以用类映射pickle流到内存对象,并可任意使用。例

该挂钩也可以让您通过网络传输Python对象,只要封装套接口,使之看上去像发送端pickle调用中的文件,以及像接收端unpickle调用中的文件。事实上,对一些人来说,pickle Python对象并在一个值得信赖的网络上传输,是替代如SOAP和XML-RPC之类网络传输协议的一个简单方法;只要通信的两端都有Python(被pickle的对象是用Python专有的格式表达的,而不是用XML文本)。

Pickle实战

pickle 模块提供了以下函数对: dumps(object) 返回一个字符串,它包含一个 pickle 格式的对象; loads(string) 返回包含在 pickle 字符串中的对象; dump(object, file) 将对象写到文件,这个文件可以是实际的物理文件,但也可以是任何类似于文件的对象,这个对象具有 write() 方法,可以接受单个的字符串参数; load(file) 返回包含在 pickle 文件中的对象。

缺省情况下, dumps() 和 dump() 使用可打印的 ASCII 表示来创建 pickle。两者都有一个 final 参数(可选),如果为 True ,则该参数指定用更快以及更小的二进制表示来创建 pickle。 loads() 和 load() 函数自动检测 pickle 是二进制格式还是文本格式。

dumps() 和 loads() 的演示

>>> import cPickle as pickle  
>>> t1 = ('this is a string', 42, [1, 2, 3], None)  
>>> t1  
('this is a string', 42, [1, 2, 3], None)  
>>> p1 = pickle.dumps(t1)  
>>> p1  
"(S'this is a string'/nI42/n(lp1/nI1/naI2/naI3/naNtp2/n."  
>>> print p1  
(S'this is a string'  
I42  
(lp1  
I1  
aI2  
aI3  
aNtp2  
.  
>>> t2 = pickle.loads(p1)  
>>> t2  
('this is a string', 42, [1, 2, 3], None)  
>>> p2 = pickle.dumps(t1, True)  
>>> p2  
'(U/x10this is a stringK*]q/x01(K/x01K/x02K/x03eNtq/x02.'  
>>> t3 = pickle.loads(p2)  
>>> t3  
('this is a string', 42, [1, 2, 3], None)  

注:该文本 pickle 格式很简单,这里就不解释了。事实上,在 pickle 模块中记录了所有使用的约定。我们还应该指出,在我们的示例中使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大的效率。然而,在实际使用复杂对象的系统中,您会看到,使用二进制格式可以在大小和速度方面带来显著的改进。

dump() 和 load() 示例

这些示例用到了 dump() 和 load() ,它们使用文件和类似文件的对象。这些函数的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 — dump() 函数能一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。清单 2 显示了这种能力的实际应用:

>>> a1 = 'apple'  
>>> b1 = {1: 'One', 2: 'Two', 3: 'Three'}  
>>> c1 = ['fee', 'fie', 'foe', 'fum']  
>>> f1 = file('temp.pkl', 'wb')  
>>> pickle.dump(a1, f1, True)  
>>> pickle.dump(b1, f1, True)  
>>> pickle.dump(c1, f1, True)  
>>> f1.close()  
>>> f2 = file('temp.pkl', 'rb')  
>>> a2 = pickle.load(f2)  
>>> a2  
'apple'  
>>> b2 = pickle.load(f2)  
>>> b2  
{1: 'One', 2: 'Two', 3: 'Three'}  
>>> c2 = pickle.load(f2)  
>>> c2  
['fee', 'fie', 'foe', 'fum']  
>>> f2.close()  

检索所支持的格式

>>> pickle.format_version  
'1.3'  
>>> pickle.compatible_formats  
['1.0', '1.1', '1.2']  

对象引用的维护

在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过 pickle 的对象维护这种行为方面丝毫没有困难

>>> a = [1, 2, 3]  
>>> b = a  
>>> a  
[1, 2, 3]  
>>> b  
[1, 2, 3]  
>>> a.append(4)  
>>> a  
[1, 2, 3, 4]  
>>> b  
[1, 2, 3, 4]  
>>> c = pickle.dumps((a, b))  
>>> d, e = pickle.loads(c)  
>>> d  
[1, 2, 3, 4]  
>>> e  
[1, 2, 3, 4]  
>>> d.append(5)  
>>> d  
[1, 2, 3, 4, 5]  
>>> e  
[1, 2, 3, 4, 5]  

递归引用

>>> l = [1, 2, 3]  
>>> l.append(l)  
>>> l  
[1, 2, 3, [...]]  
>>> l[3]  
[1, 2, 3, [...]]  
>>> l[3][3]  
[1, 2, 3, [...]]  
>>> p = pickle.dumps(l)  
>>> l2 = pickle.loads(p)  
>>> l2  
[1, 2, 3, [...]]  
>>> l2[3]  
[1, 2, 3, [...]]  
>>> l2[3][3]  
[1, 2, 3, [...]]  

循环引用

>>> a = [1, 2]  
>>> b = [3, 4]  
>>> a.append(b)  
>>> a  
[1, 2, [3, 4]]  
>>> b.append(a)  
>>> a  
[1, 2, [3, 4, [...]]]  
>>> b  
[3, 4, [1, 2, [...]]]  
>>> a[2]  
[3, 4, [1, 2, [...]]]  
>>> b[2]  
[1, 2, [3, 4, [...]]]  
>>> a[2] is b  
1  
>>> b[2] is a  
1  
>>> f = file('temp.pkl', 'w')  
>>> pickle.dump((a, b), f)  
>>> f.close()  
>>> f = file('temp.pkl', 'r')  
>>> c, d = pickle.load(f)  
>>> f.close()  
>>> c  
[1, 2, [3, 4, [...]]]  
>>> d  
[3, 4, [1, 2, [...]]]  
>>> c[2]  
[3, 4, [1, 2, [...]]]  
>>> d[2]  
[1, 2, [3, 4, [...]]]  
>>> c[2] is d  
1  
>>> d[2] is c  
1  

分别 pickle vs. 在一个元组中一起 pickle

注意,如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果,如下所示:

>>> f = file('temp.pkl', 'w')  
>>> pickle.dump(a, f)  
>>> pickle.dump(b, f)  
>>> f.close()  
>>> f = file('temp.pkl', 'r')  
>>> c = pickle.load(f)  
>>> d = pickle.load(f)  
>>> f.close()  
>>> c  
[1, 2, [3, 4, [...]]]  
>>> d  
[3, 4, [1, 2, [...]]]  
>>> c[2]  
[3, 4, [1, 2, [...]]]  
>>> d[2]  
[1, 2, [3, 4, [...]]]  
>>> c[2] is d  
0  
>>> d[2] is c  
0  

作为原来对象副本的被恢复的对象

>>> j = [1, 2, 3]  
>>> k = j  
>>> k is j  
1  
>>> x = pickle.dumps(k)  
>>> y = pickle.loads(x)  
>>> y  
[1, 2, 3]  
>>> y == k  
1  
>>> y is k  
0  
>>> y is j  
0  
>>> k is j  
1  

维护分别 pickle 的对象间的引用

>>> f = file('temp.pkl', 'w')  
>>> pickler = pickle.Pickler(f)  
>>> pickler.dump(a)  
<cPickle.Pickler object at 0x89b0bb8>  
>>> pickler.dump(b)  
<cPickle.Pickler object at 0x89b0bb8>  
>>> f.close()  
>>> f = file('temp.pkl', 'r')  
>>> unpickler = pickle.Unpickler(f)  
>>> c = unpickler.load()  
>>> d = unpickler.load()  
>>> c[2]  
[3, 4, [1, 2, [...]]]  
>>> d[2]  
[1, 2, [3, 4, [...]]]  
>>> c[2] is d  
1  
>>> d[2] is c  
1  

试图 pickle 文件对象的结果

一些对象类型是不可 pickle 的。例如,Python 不能 pickle 文件对象(或者任何带有对文件对象引用的对象),因为 Python 在 unpickle 时不能保证它可以重建该文件的状态(另一个示例比较难懂,在这类文章中不值得提出来)。试图 pickle 文件对象会导致以下错误:

>>> f = file('temp.pkl', 'w')  
>>> p = pickle.dumps(f)  
Traceback (most recent call last):  
  File "<input>", line 1, in ?  
  File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce  
    raise TypeError, "can't pickle %s objects" % base.__name__  
TypeError: can't pickle file objects  

Pickler协议和cPickle

在最近的Python版本中,pickler推出了协议的概念:pickle数据的保存格式。通过pickle调用时传入一个额外的参数,可指定所需的协议(但unpickle调用不需要:协议是自动从已pickle的数据确定的):

pickle.dump(object, file, protocol)

Pickle数据可以按文本协议或二进制协议产生。默认情况下,存储协议是文本协议(也称为0号协议)。在文本模式下,用来存储pickle对象的文件可以用文本模式打开,如上述的例子,并且pickle的数据是可打印的ASCII文本,并且是可读的(这基本上是对堆栈机实现的指示)。

其他协议(1号和2号协议 )以二进制格式存储pickle数据,并要求文件以二进制模式打开(例如:rb、wb)。1号协议是原始二进制格式;2号协议是Python 2.3增加的,它改善了对新型类pickle的支持。二进制格式效率更高一点,但它无法进行查看。旧的pickle调用有一个选项,即bin参数,现已被归入使用大于0的协议。pickle模块还提供了一个HIGHEST_PROTOCOL变量,传入它可以自动选择最大的协议值。

注意:如果您使用默认的文本协议,以后请务必以文本模式打开pickle文件。在一些平台上,因为Windows的行尾格式不同,以二进制模式打开文本数据可能会导致unpickle错误:

>>> f = open('temp', 'w')                  # text mode file on Windows
>>> pickle.dump(('ex', 'parrot'), f)       # use default text protocol
>>> f.close( ) 
>>>
>>> pickle.load(open('temp', 'r'))         # OK in text mode
('ex', 'parrot')
>>> pickle.load(open('temp', 'rb'))        # fails in binary
Traceback (most recent call last):
  File "<pyshell#337>", line 1, in -toplevel-
    pickle.load(open('temp', 'rb'))
 ...lines deleted...
ValueError: insecure string pickle

回避这个潜在问题的方法之一是,总是使用二进制模式的文件,即使是用文本pickle协议。至少对于二进制pickler协议(高于默认0),您必须以二进制模式打开文件,所以这不是一个坏习惯:

>>> f = open('temp', 'wb')                 # create in binary mode
>>> pickle.dump(('ex', 'parrot'), f)       # use text protocol
>>> f.close( )
>>>
>>> pickle.load(open('temp', 'rb'))
('ex', 'parrot')
>>> pickle.load(open('temp', 'r'))
('ex', 'parrot')

请参考Python库手册,以了解更多pickler的信息。另外,请查阅marshal,它也是一个序列化对象的模块,但只能处理简单对象类型。pickle比marshal更通用,并通常是首选。

而当你翻看(或点击)Python手册时,请一定也要看看cPickle模块的条目,它是pickle的C语言实现,性能上更快。您可以显式导入cPickle替代pickle,以大幅提升速度;其主要的限制是,你不能继承该版本的Pickle和Unpickle,因为它们是函数,而不是类(多数程序并不要求它们是类)。pickle和cPickle模块使用兼容的数据格式,所以它们可以互换使用。

如果您的Python中有shelve模块,它会自动选用cPickle模块,而不是pickle,以达到更快的序列化。我还没有解释过shelve,但我马上就会讲到它。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值