《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等。用XDRXML等。

 

12.2加载并保存对象

保存时需要先转换成一个可以被读回的字节串,对应Java中的marshalingC++中的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对象,则使用PicklerUnpickler类是很方便的。也可以细分来定制pickler

cPickle模块是C语言实现的pickle模块,比纯Python模块速度快几个数量级,且格式完全兼容,但是不可以再细分PicklerUnpickler

>>> 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字符串。

如果Cintlong的大小相同,T格式字符会把给定的编号解包为Python长整型。如果C intC 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'

<tr valign="t