《Python2.1宝典》笔记12-14章的内容介绍
《Python2.1宝典》笔记12-14章的内容介绍
第十二章 存储数据和对象
12.1数据存储概述
12.1.1文本与二进制对比
文本格式易于阅读和调试,跨平台性能好。二进制格式占用空间小,适于按记录存龋
12.1.2压缩
假如对象的大小开始成为问题,或者需要在网络上传输。
12.1.3字节次序(Endianness)
处理器把多字节数字存放在内存中可以是big-endian(低端优先),也可以是little-endian(高端优先):
>>> import sys
>>> print '"... %s-endian",Gulliver said.' % sys.byteorder
"...little-endian",Gulliver said. #在Intel的机器上
大多数Python程序不需要关心这些细节。但如果需要在平台之间传递数据则要考虑。
12.1.4对象状态
某些状态已经不在Python可以控制的范围之内了,比如一个Socket就不需要保存。
12.1.5目的地
磁盘文件、数据库、网络
12.1.6在接收端
可能不仅仅是Python的,可能有C等。用XDR、XML等。
12.2加载并保存对象
保存时需要先转换成一个可以被读回的字节串,对应Java中的marshaling或C++中的serialization,在Python中称为pickling。
12.2.1采用pickle进行转换
pickle模块把很多Python对象转换为字节表示法,或从字节表示法转换回来:
>>> import pickle
>>> stuff=[5,3.5,'Alfred']
>>> pstuff=pickle.dumps(stuff)
>>> pstuff
"(lp0/01215/012aF3.5/012aS/Alfred)/012pl/012a."
>>> pickle.loads(pstuff)
[5,3.5,'Alfred']
上面的pstuff是一个字节串,便于传输了。
pickle.dumps(object[,bin])函数返回对象的串接格式,而pickle.dump(object,file[,bin])把串接格式发给已打开的、类似文件的对象。bin参数默认为0,按照文本格式转换对象,当为1时按照紧凑但难懂的二进制格式保存。每种格式都是跨平台的。
pickle.loads(str)函数将从字节串构造对象。pickle.load(file)从类似文件的对象读取已转换格式的对象,并返回原始的,未转换格式的对象。
下例:
>>> s=StringIO.StringIO() #创建临时文件对象
>>> p=pickle.Pickler(s,1) #二进制类型串行化
>>> p.dump([1,2,3])
>>> p.dump('Hello!')
>>> s.getvalue() #检查pickel格式
'[q/000(K/001K/002K/0C3e.0/006Hello!/q001.'
>>> s.seek(0) #重设文件指针到开始位置
>>> u=pickle.Uppickler(s)
>>> u.load()
[1,2,3]
>>> u.load()
'Hello!'
对于同一类型的连续存储也可以很好的区分。如果需要转换多种格式,(by gashero)或者传递pickler对象,则使用Pickler和Unpickler类是很方便的。也可以细分来定制pickler。
cPickle模块是C语言实现的pickle模块,比纯Python模块速度快几个数量级,且格式完全兼容,但是不可以再细分Pickler和Unpickler。
>>> import cPickle,pickle
>>> s=cPickle.dumps(('one':1,'two':2))
>>> pickle.loads(s)
['one':1,'two':2]
由于升级,各个版本之间的格式可能不同,但是pickle模块是可以自动识别出隐藏再数据中的版本号的,并自动处理。
>>> pickle.format_version
'1.3'
>>> pickle.compatible_formats
['1.0','1.1','1.2'] #能够正确读取的旧版本
试图读取一个unpickle不支持的版本会产生异常。
允许转换的数据类型
数字、字符串、None、包含"可转换格式"对象的包容器(元组、列表、词典)。
当转换一个内置函数、自己的函数或类的对象时,pickle会把数据和类型名和类型所属的模块名一起存储,但是不会存储类型的实现。unpickle恢复对象时,pickle首先导入原属的模块,因此转换一定要在该模块的最上层定义函数或类。
保存实例对象,用pickle.__getstate__方法,返回对象的状态。Python加载对象时,pickle自动按照存储中的类型创建新对象,并调用对象的__setstate__方法,传递一个元组参数来恢复对象状态。
class Point:
def __init__(self,x,y):
self.x=x;self.y=y
def __str__(self):
return '(%d,%d)' % (self.x,self.y)
def __getstate__(self):
print "正在备份对象状态"
return (self.x,self.y)
def __setstate__(self,state):
print "正在恢复对象状态"
self.x,self.y=state
p=Point(10,20)
z=pickle.dumps(p) #正在备份对象状态
newp=pickle.loads(z) #正在恢复对象状态
print newp #(10,20)
如果对象没有__getstate__成员,则pickle保存__dict__的内容。unpickle对象时,load函数通常不会调用对象的构造函数(__init__),如果确实需要,则可用__getinitargs__方法,再重新加载这个对象时,会把参量传递给__init__。
使用copy_reg模块,可以为C扩展模块中的数据类型添加转换格式的支持。要添加这种支持,调用copy_reg.pickle(type, reduction_func [,constructor_ob]),来为给定的类型注册一个缩减函数和一个构造函数。缩减函数reduction_func获取一个对象,返回二元组,第一个元素是该对象的构造函数,第二个元素也是一个元组,是构造函数的参数列表。在为新类型注册了自己的函数之后,任意串接的此类型对象都可以使用。
其他的转换格式问题
格式转换实例时不会存储类实现,所以可以在确保不破坏已经存储的格式数据情况下更改类的定义,设置恢复以前保存的实例对象。
存储和恢复类实例时,对其他对象的引用也是保存的,比如下例。对同一个列表z的引用在恢复之后仍然有效。
>>> z=[1,2,3]
>>> y=[z,z]
>>> y[0] is y[1] #两个引用相同,是同一对象
1
>>> s=pickle.dumps(y)
>>> x=pickle.loads(s)
>>> x
[[1,2,3],[1,2,3]]
>>> x[0] is x[1] #两个成员还是引用同一对象
1
注意:转换一个对象之后,修改它,之后再转换,则只保存此对象的第一个实例版本。
如果转换到一个类似文件的对象时发生了错误,则产生PickImgError异常。已经写入文件的内容是无法预测的,而且不再有用。
12.2.2marshal模块
pickle模块的实现中调用marshal完成一些工作。marshal的一个优点(pickle不具备)是可以处理代码对象的实现(比如函数)。如下是例子:
>>> def adder(a,b):
... return a+b
>>> adder(10,2)
12
>>> import marshal
>>> s=marshal.dumps(adder.func_code)
>>> def newadder():
... pass
>>> newadder.func_code=marshal.loads(s)
>>> newadder(20,10)
30
参考33章介绍代码对象和Python对象。
12.3示例:通过网络移动对象
示例所有的格式转换,swap模块,创建一个后台进程在两个交互模式下的Python解释器之间发送对象。同样允许在两台电脑之间发送对象。
参考15章的网络和26章的线程。
调用swap.send(obj)方法发送对象,对方的接收程序收到后存在swap.obj中。启动Python解释器时使用-c(告知执行import swap命令)和-i(执行命令后仍然保持运行)两个选项。这个特征允许读者用已经加载的swap模块启动并运行。
from socket import *
import cPickle,threading
ADDR="127.0.0.1"
PORT=50000
bConnected=0
def send(obj):
"Sends an object to a remote listener"
if bConnected:
conn.send(cPickle.dumps(obj,1))
else:
print 'Not connected!'
def listenThread():
"Receives objects from remote side"
global bServer, conn, obj, bConnected
while 1:
s=socket(AF_INET,SOCK_STREAM)
try:
s.bind((ADDR,PORT))
s.listen(1)
bServer=1
conn=s.accept()[0]
except Exception, e:
#可能已经被使用过了,所以只做客户端
bServer=0
conn=socket(AF_INET,SOCK_STREAM)
conn.connect((ADDR,PORT))
#永久的接受连接
bConnected=1
while 1:
o=conn.recv(8192)
if not o:
break;
obj=cPickle.loads(o)
print 'Receive new object'
print obj
bConnected=0
#开始监听线程
threading.Thread(target=listenThread).start()
print '线程开始监听'
为了简单起见,程序中省略了很多错误检查,如果真正的应用需要自己加上。listenThread函数保持循环,等待对象的到来。第一次启动时listenThread会尝试绑定到指定位置,(by gashero)如果绑定失败则会尝试连接那个地址。
12.4使用类似数据库的存储
shelve模块允许把Python对象保存在类似dbm的模块中。
dbm和其他数据库信息参考14章。
shelve.open(file[,mode])打开并返回一个shelve对象。mode参数(与dbm.open中的模式相同),默认值为c,意味着读写打开数据库,且不存在时创建。使用完成后用shelve对象的close()方法关闭。
把数据库看作词典来访问:
>>> import shelve
>>> db=shelve.open('objdb') #不要使用文件扩展名
>>> db['secretCombination']=[5,73,17]
>>> db['account']=5671012
>>> db['secretCombination']
[5,23,17]
>>> del db['account']
>>> db.has_key('account')
0
>>> db.keys()
['secretCombination']
>>> db.close()
shelve模块使用pickle,可以存储pickle可以处理的类型。shelve的限定与dbm限度相同。尽量不要用于存储太过于庞大的对象。
12.5转换到C结构或从C结构转换回来
struct模块允许创建一个等效于C结构的字符串,可以读写那些非Python程序生成的二进制文件。或者用于不同程序的网络通信。因为pickle模块的数据类型只能被Python识别。
使用struct需要使用格式字符串,调用struct.pack(format,v1,v2, ...)。格式字符如下:
字符 |
C类型 |
Python类型 |
---|---|---|
c |
Char |
长度为1的字符串 |
s |
char[] |
字符串 |
p |
(pascal字符串) |
字符串 |
i |
Int |
整型 |
I |
Unsigned int |
整型或长整型* |
b |
Signed char |
整型 |
B |
unsigned char |
整型 |
h |
Short |
整型 |
H |
unsigned short |
整型 |
l |
Long |
整型 |
Long |
unsigned Long |
长整型 |
f |
Float |
浮点型 |
d |
Double |
浮点型 |
x |
(pad style) |
- |
P |
void* |
整型或长整型 |
带有星号的表示依赖于平台的指针是32位还是16位。
例如,如下的C结构的等价物用:
struct {
int a;
int b;
int c;
};
采用值10,20,'Z',如下:
>>> import struct
>>> z=struct.pack('iic',10,20,'Z')
>>> z
'/012/000/000/000/024/000/000/000z'
从字节串反向转换用struct.unpack(format,data),返回元组:
>>> struct.unpack('iic',z)
(10,20,'Z')
传递给unpack的格式字符串一定要说明字符串中的所有数据,否则会产生异常。使用struct.calcsize(format)可以计算给定的格式字符串占用的字节数。
可以在格式字符前加上一个编号,表示这个数据类型重复的次数。为了便于理解,可以在格式字符串中的格式字符之间加入空格。
重复器编号的运行方式与's'(字符串)格式字符稍有差别。重复器会告诉字符串的长度(5s意味着5个字符的字符串)。0s意味着一个空字符串,而0c意味着0字符串。
如果C的int和long的大小相同,T格式字符会把给定的编号解包为Python长整型。如果C int比C long小,T把编号转换为Python整数。
'p'格式字符串支持pascal字符串。这种字符串使用第一个字节存储字符串长度,所以最大长度为255字节,其余的截断。如果提供了重复器则是指定整个字符串的字节数,包含长度字节。如果字符串小于指定字节数,则pack会添加空的填充字符。
默认时,struct会把字节顺序和结构成员对齐使用当前平台的C编译器使用的格式。通过下表列出的某个修饰符启动自己的格式字符串,可以超越这种行为。例如使用网络序:
>>> struct.pack('ic',65535,'D') #本机字节序为高序优先
'/377/377/000/000D'
>>> struct.pack('!ic',65535,'D') #强制网络字节序
'/000/000/377/377D'