python 宝典 笔记 第十二章 存储数据和对象 (各种对象转换成字符串)

 

第十二章 存储数据和对象

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'

修饰符

字节顺序

对齐

大小

<

高序优先(little-endian)

标准

>或!

低序优先(网络)

标准

=

内在的

标准

@(默认)

内在的

内在的

内在的

当使用了一个大小为"standard"的修饰符,则C short会占据2个字节,int、long、float将使用4个字节,double使用8个字节。

如果需要使用对齐,而又不想用@(内在的对齐)时,可用'x'格式字符插入填充字节。如果必须强制一个结构的末端依据特殊类型的对齐规则进行对齐,则可用这种类型的格式代码(计数值为0)。如下例子强制单字符的结构对齐整型边界:

>>> struct.pack('c','A')

'A'

>>> struct.pack('c0l','A')

'A/000/000/000'

'P'(指针)格式字符只能在本地赋值中使用。

可用struct模块读写二进制文件,比如提取.wav文件的前36个字节的文件信息头。内容如下:

"RIFF"(4字节),用于识别文件类型

高序优先的长度字段(4字节)

"WAVE"(4字节),用于识别文件类型

"fmt "(4字节)

格式定义的子数据块长度(4字节)

格式类型(2字节)

声道数(2字节)

采样频率/Hz(4字节)

每秒数据量/字节(4字节)

样本字节数/字节(2字节)

声道宽度/位(2字节)

表示标题的格式字符串:

'<4s i 4s 4s ihhiihh'

如下是提取WAV文件头信息的例子:

>>> s=open('c://winnt//media//ringin.wav','rb').read(36)

>>> struct.unpack('<4si4s4sihhiihh',s)

('RIFF',10018,'WAVE','fmt ',16,1,1,11025,11025,1,8)


12.6把数据转换为标准格式

使用struct模块生成具有工业标准格式的数据。

12.6.1SunXDR格式

XDR(eXternal Data Representation,外部数据表示法)格式由Sun Microsystems创建的标准数据格式。RFC1832定义了这种格式,最通用的用法在NFS(Network File System,网络文件系统)中。适合在各个平台和系统之间共享数据。

xdrlib模块实现了XDR格式的子集,去掉了罕见使用的数据类型。转换到XDR只需创建xdrlib.Packer类的实例,反向转换需要创建xdrlib.Unpacker的实例。

Packer的例子:

>>> import xdrlib

>>> p=xdrlib.Packer() #构造函数无需参数

>>> p.pack_float(3.5) #32位浮点数。多种pack_<type>方法

>>> p.pack_double(10.5) #64位浮点数

>>> p.pack_int(-15) #32位有符号整数

>>> p.pack_uint(15) #32位无符号整数

>>> p.pack_hyper(100) #64位有符号整数

>>> p.pack_uhyper(200) #64位无符号整数

>>> p.pack_enum(3) #枚举类型

>>> p.pack_bool(1) #布尔型,取1或0

>>> p.pack_bool('Hi') #值为True,所以存储1

pack_fstring(count,str)方法打包固定长度的字符串,长度为count。该函数不会存储字符串的大小,所以要解包时需要预先知道长度。pack_string(str)函数先调用pack_uint写入字符串长度,然后再用pack_fstring()来写入字符串,这样就可以指定不定长度的字符串了。

Packer对象还包含pack_bytes和pack_opaque方法,但是只是调用pack_string,而pack_fopaque实际上只调用了pack_fstring方法。

pack_farray(count,list,packFunc)函数打包一个长度固定,数据类型相同的数组(count是数据项长度)。要求计数值与列表长度必须相同,愚蠢的一点。且解包时,必须自己知道数组的长度。同样存在pack_array(list,packFunc),可以自己存储打包长度,在存储数组。packFunc告知Packer打包每个项所使用的方法,例如所有数据项都是整数,则为如下:

>>> p.pack_array([1,2,3,4],p.pack_int)

pack_list(list,packFunc)方法也是打包存储数组,但是可以支持预先并不知道长度的数组。例如一个自定义的不完整序列:

>>> class MySeq:

... def __getitem__(self,i):

... if i<5:

... return i

... raise IndexError

>>> m=MySeq()

>>> for i in m:

... print i

0

1

2

3

4

>>> p.pack_list(m,p.pack_int)

使用get_buffer()方法返回字节串,表达已经打包的所有数据的包格式。reset()方法清空缓冲区。

>>> p.reset()

>>> p.pack_int(10)

>>> p.get_buffer()

'/000/000/000/012'

>>> p.reset()

>>> p.get_buffer()

''

Unpacker对象

每个类型都有pack_<type>与unpack_<type>方法对应。

>>> import xdrlib

>>> p=xdrlib.Packer()

>>> p.pack_float(2.0)

>>> p.pack_fstring(4,'Dave')

>>> p.pack_string('/export/home')

>>> u=xdrlib.Unpacker(p.get_buffer())

>>> u.unpack_float()

2.0

>>> u.unpack_fstring(4)

'Dave'

>>> u.unpack_string()

'/export/home'

>>> u.done()

done()方法通知Unpacker,已经完成了解码数据。因为如果Unpacker的缓冲区如果还有数据,则会产生Error异常,告知用户还有剩余数据。

调用reset(str)可以使用str的数据用于解码,并替换缓冲区。任何时候都可用get_buffer()检索数据流的字符串表示法。

可用get_position()和set_position(pos)获取和设置解码位置。

12.6.2其他格式

比较推荐的也只有XML格式,更多信息参见18章。其他的特定文件格式很容易从网上找到定义。


12.7压缩数据

12.7.1zlib

包含GZIP和ZIP文件的压缩支持。

最简单的用法为compress(str[,level]和decompress(str [,wbits [,bufsize]])函数。压缩级别的level可用1到9,默认6,数值大则压缩率高而速度慢。解压缩用的wbits控制历史缓冲区大小,有8到15(默认)的数值,这个数值越大耗内存越多,但是会支持更好的压缩。bufsize设置缓冲区,默认16384,推荐不必自己修改。读入数据和返回数据都是按照字节串进行。

>>> import zlib

>>> longstring=100*'That zlib module sure is fun!'

>>> compressed=zlib.compress(longstring)

>>> len(longstring); len(compressed)

2900

62 @code: #zlib的压缩后数据

>>> zlib.decompress(compressed)[:40] # by gashero

'That zlib module sure is fun!That zlib m'

有关zlib的更多信息参阅如下

http://www.info-zip.org/pub/infozip/zlib/

zlib模块包含两函数crc32(str[,value])和adler32(str[,value]) ,都是用于计算字节串的校验和。value是校验和的起始值,用于支持多块的联合校验值。

>>> data='My dog has no fleas!'

>>> zlib.adler32(data)

1193871046

>>> data=data[:5]+'z'+data[6:]

>>> data

'My doz has no fleas!' #修改了一个字节

>>> zlib.adler32(data)

1212548825 #校验和的数值发生了改变

crc32的返回值比adler32更为可靠,但是计算时间更长。

如果需要压缩的数据量比允许用的内存要多,则可用zlib创建压缩和解压缩对象。compressobj([level])创建压缩对象。重复调用此对象的方法compress(str),每次调用都会压缩并保存数据。方法flush([mode]) 将完成压缩并返回剩余数据。

>>> c=zlib.compressobj(9)

>>> out=c.compress(1000*'I will not throw knives')

>>> out+=c.compress(2000*'or chairs')

>>> out+=c.flush()

>>> len(out)

115

如果mode使用Z_FULL_FLUSH或Z_SYNCH_FLUSH,会返回当前缓冲区的所有压缩数据,并且还可以继续添加压缩数据。如果忽略mode,则对象认为压缩完成,不需要添加数据了。

通过实际测试。仅在第一次调用c.compress()时,有返回2个字节的数据,之后的调用不会返回任何东西。单独调用c.flush()返回的数据并不是完整的,需要与上句的2个字节返回数据加在一起才可以解压缩。

通过zlib的decompressobj([wbits])函数,可以创建解压缩对象,允许一块一块的解压缩数据流。调用decompress(str)方法能够解压缩下一数据块。decompress能返回它能够完成的、最大量的解压缩数据,但是因为需要缓冲一些额外数据,直到读者提供了更多要解压缩的数据为止。如下代码解压缩上例的输出,一次20字节:

>>> d=zlib.decompressobj() #创建解压缩对象

>>> msg=''

>>> while out:

... msg+=d.decompress(out[:20]) #解压缩一部分

... out=out[20:]

>>> msg+=d.flush() #告知所有数据读入完成

>>> len(msg)

24800

提供了足够的数据之后就用flush()方法告知,之后就不能再用decompress()方法了。

解压缩对象还包含unused_data成员,保留了上次调用decompress之后剩余的压缩数据。非空的unused_data字符串意味着解压缩对象还在处理额外的数据,以便完成解压缩这块特殊数据。

12.7.2gzip

gzip模块允许读写.gz(GNU zip)文件,如同读写普通文件一样,忽略压缩与解压缩的细节。

GNU zip和gunzip程序还支持其他多种格式,但是gzip模块只支持一种。

gzip.GzipFile([filename[,mode[,compresslevel[,fileobj]]]])函数构造GzipFile对象,必须提供filename或fileobj参量,文件对象可以是类似文件的任何对象,如cStringIO对象。compresslevel提供压缩等经。

如果没有mode,则gzip会试着使用fileobj的模式,如果也没有则默认用'rb'打开。GzipFile不能为同时读写打开,可以用rb、wb、ab模式。

为了实现与普通文件相同的接口。可用gzip模块的open(filename [,mode[,level]])函数。类似于Python内置的打开文件的方法。

>>> f=gzip.open('small.gz','wb')

>>> f.write("""Old woman!

... Man"""

>>> f.close()

>>> f=gzip.open('small.gz')

>>> print f.read()

Old woman!

Man

zipfile模块允许读、写和获得有关以通用ZIP文件格式存储的文件信息。zipfile模块暂时不支持ZIP文件注释和跨越多个磁盘的文件。

如果给定文件确实是ZIP文件,则zipfile.is_zipfile(filename)返回真。zipfile模块还定义了ZipFile、ZipInfo、PyZipFile类。

ZipFile类,构造函数ZipFile(filename[,mode[,compression]])

>>> import zipfile

>>> z=zipfile.ZipFile('room.zip')

>>> z.printdir() #按可读格式打印存档列表

File Name Modified Size

world 2000-09-05 09:25:14 10919

cryst.cfg 1999-03-07 06:14:34 27

mode可以是r(读,默认值)、w(写)或a(附加)。如果附加到ZIP文件中,会添加新文件,如果附加到非ZIP文件,则会在文件末尾添加一个ZIP存档,而且并非所有的程序都可以正确打开。compression参数可为ZIP_STORED(不压缩)和ZIP_DEFLATED(压缩)。

ZipFile对象的namelist()方法将返回ZIP包含的文件的列表。采用getinfo(name)方法返回任意文件的ZipInfo对象。采用infolist()方法可以获得整个存档的ZipInfo的列表:

>>> z.namelist()

['world','cryst.cfg'] #ZIP档案包含两个文件

>>> z.getinfo('world') #获取文件world的信息

<zipfile.ZipInfo instance at 010FD14C>

>>> z.getinfo('world').file_size

10919

>>> z.infolist()

[<zipfile.ZipInfo instance at 010FD14C>,

<zipfile.ZipInfo instance at 010E116C>]

如果以读或者附加模式打开ZIP文件,则read(name)会解压指定的文件,返回内容。testzip()方法返回第一个已损坏文件的名字,如果所有文件都没有损坏返回None。

如果以写或者附加模式打开ZIP文件,则write(filename[,arcname [,compress_type]])函数把filename文件的内容添加到存档中。如果给arcname提供一个值,则是存档中的文件名。compress_type提供超越创建ZipFile时使用的压缩类型。

更改ZIP文件后,用close()方法保存。ZipFile对象还有debug属性,可以用这个属性更改调试输出信息的层次。最大输出为3,最小的没有输出为0(默认)。

ZipInfo,提供了存档中每个成员的信息。构造函数ZipInfo( [filename[,date_time]])。getinfo()和infolist()也能返回ZipInfo对象。filename应该是文件的完整路径,而且date_time是个六元组,其中包含最后一次修改时间的信息。ZipInfo包含很多属性,如下最有用的:

名字

说明

filename

存档文件名

compress_size

压缩后大小

file_size

原始文件大小

date_time

最后一次修改的日期时间。年、月日、时、分、秒

compress_type

压缩类型(stored或deflated)

CRC

原始文件的CRC32

comment

项注释

extract_version

解压所需最小软件版本

header_offset

文件标题的字节偏移量

file_offset

文件数据的字节偏移量

PyZipFile,用于创建包含Python模块和包的ZIP文件,是ZipFile的一个子类,构造函数同ZipFile。

PyZipFile添加的唯一方法是writepy(pathname),会搜索*.py文件,并且把响应的字节码文件添加到ZIP文件中。如模块file.py会存档为file.pyo,如果不存在则用file.pyc,如果还不存在则编译创建file.pyc来添加到存档中。

pathname如果是包目录的名字(即包含__init__.py文件的目录),writepy会递归搜索这个目录,找到所有*.py文件。如果pathname只是普通目录名,则只搜索这个目录下的*.py文件,不递归搜索子目录。如pathname只是一个普通的Python模块,writepy就会把它的字节码添加到ZIP文件中。

关于Python包,参考6章。


完成...




--------------------------------------------------------





 

第十三章 访问日期和时间

13.1Python中告知时间

可以把时间表示为数字或元组,time模块提供了时间函数。

13.1.1滴答

把时间中的一个点表示为"滴答"的次数,即新纪元后所经历的秒数。新纪元是任意选择的"时间开始"。对UNIX和Windows来说,新纪元指1/1/1970的上午12:00。

time.time返回当前时间,以滴答为单位。Python会为滴答使用浮点值。因为时间精度是系统相关的,有些系统上time.time总是返回整数值。

滴答的格式是很容易处理的,但是在1970年之前或者2038年之后的日期是无法用滴答表示的,第三方模块如mxDateTime提供了更大范围的日期时间值。

13.1.2时间元组

Python中很多函数以9元素元组表示时间:

索引

0

4位数字年

1993

1

1-12

2

1-31

3

小时

0-23(0代表12a.m.)

4

分钟

0-59

5

0-61(60或61是闰秒)

6

星期

0-6(0代表星期一)

7

一年中的哪一天

1-366(古罗马历)

8

Daylight savings

-1,0,1

由于元组的元素排列是有顺序的,所以可以对元组进行比较。TimeTuple不包含时区。

13.1.3秒表时间

clock函数可以为Python代码计时。比如在一个函数调用前和调用后分别调用clock,再计算差值得到经历的时间/秒。由clock返回的值是系统无关的,并且与真实的时间无关。只是用于计时。

暂停执行的函数是time.sleep(n),n是一个浮点数,暂停n秒。


13.2时间格式之间的转换

函数localtime可以把滴答转换为TimeTuple。

>>> time.localtime(time.time())

(2006,2,14,11,41,11,1,45,0)

函数gmtime也用于从滴答转换到TimeTuple,但是返回UTC,即通用协调时间。

函数mktime从TimeTuple转换到EpochSeconds(滴答)。根据本地时区解释TimeTuple。与localtime是反函数。

gmtime的反函数是calendar.timegm。


13.3解析及打印日期时间

asctime函数获取一个TimeTuple,并返回一个可阅读的时间信息。适于日志。可以带参数用于准还,或者不带参数而直接放回当前时间。不带参数的版本用于Python2.1。

函数ctime转换滴答值到时间信息。

13.3.1有趣的格式

strftime(format,timetuple)按照指定格式转换TimeTuple格式。如下是strftime的format字符串语法:

代码

替换

示例/范围

%a

缩写的星期名

Thur

%A

完整的星期名

Thursday

%b

缩写的月名

Jan

%B

完整的月名

January

%c

日期和时间表示法(等效于%x%X)

12/10/00 10:09:41

%d

月份中的一天

01-31

%H

小时(24小时制)

00-23

%h

小时(12小时制)

01-12

%j

Julian天(一年中的一天)

001-366

%m

月份

01-12

%M

分钟

00-59

%p

A.M.或P.M.

AM

%S

00-61

%L

周编号,周日开始,一年中第一个周日之前的那些周为第0周

00-53

%w

以数字表示星期几(0=星期日)

0-6

%W

周编号,每周以周一开始,一年中第一个星期一之前的那些为第0周

00-53

%x

日期

12/10/00

%X

时间

10:09:41

%y

2位数字的年

00-99

%Y

4位数字的年

2000

%Z

时区的名称

太平洋标准时间

%%

字面的%符号

%

例如实现ctime的返回格式:

>>> time.strftime("%a %b %d %H:%M:%S %Y",Now)

'Sun Dec 10 10:09:41 2000'

13.3.2解析时间

函数strptime(timestring[,format])是strftime的反函数,解析字符串,并返回一个TimeTuple。按照未指定的时间组件进行猜测,如果采用format无法解析,则会产生ValueError。默认格式是ctime所使用的格式,即"%a %b %d %H:%M:%S %Y"

在很多系统上都可以使用strptime函数,但是Windows上不可用。

13.3.3定位

不同国家的日期格式不同。例如日月,在美国是月在前,英国是日在前。strftime会考虑当前时区格式,"%x"就是使用正确的日月次序。编写代码时也要考虑这个问题,"%m/%d"并不是在所有地区都正确。可以参考34章的有关国际化的信息。


13.4访问日历

13.4.1打印月历和年历

使用calendar模块,内部使用滴答表示日期,所以可用的日历范围有限。

monthcalendar(yearnum,mouthnum)

返回一个列表的列表,表示月历,主列表表示星期,子列表是星期中的几天的日期。子列表的0值表示上个月或下个月的,用于补满7个元素。

month(yearnum,monthnum[,width[,linesperweek]])

返回一个占据多列的字符串。返回一个具有可打印格式的月历,width指定了每一栏的宽度,最小值也是默认值是2。linesperweek参数指定了每周打印多少行,默认值为1,用于在各行之间留出间隔。如下例:

>>> print calendar.month(2002,5)

May 2002

Mo Tu We Th Fr Sa Su

1 2 3 4 6

6 7 8 9 10 11 12

13 14 15 16 17 18 19

20 21 22 23 24 25 26

27 28 29 30 31

prmonth(yearnum,monthnum[,width[,linesperweek]])

打印month的相应输出。

calendar(yearnum[,width[,linesperweek[,columnpadding]])

可打印年历,每行包含3各月。columnpadding参数指明了要在月栏中添加的间隔,默认值为6。对应的prcalendar函数将会把如上函数的输出打印出来。

13.4.2日历信息

weekday函数将为特殊日期查询星期值:

weekday(year,month,day)

星期值的范围是从Monday(0,星期一)至Sunday(6,星期日)。为了方便使用可以全部用常量:

>>> calendar.weekday(2002,5,1)==calendar.WEDNESDAY

monthrange(yearnum,monthnum)

函数返回二元组,在yearnum年中monthnum月的第一天的周日,及月份的长度。

>>> calendar.monthrange(2000,2) #闰年的闰月

(1,29)

默认情况下calendar将把Monday作为一周的第一天,而Sunday是最后一天。通过setfirstweekday(weekday)可以更改。函数firstweekday可以返回哪一天是每周的第一天的设置。

13.4.3闰年

如果yearnum是闰年则isleap(yearnum)则返回True。

leapdays(firstyear,lastyear)

返回从firstyear到lastyear之间的闰天数。


13.5使用时区

time.daylight值指明是否定义了本地的DST(Daylight Saving Time)时间区。当他值为True时,则DST时区可用。

time.timezone值是从本地时区到UTC的偏移量,以秒为单位。time.altzone值是从本地DST时区到UTC的偏移量/秒。altzone更准确,但是只有在time.daylight=True时才可用。

time.tzname值是一个元组,第一个项是本地时区名称。如果有效,则第二个项是Daylight Saving Time时区的名称。只有time.daylight=True时第二个项才可用。例:

>>> time.tzname

('Pacific Standard Time','Pacific Daylight Time')


13.6允许两位数字的年

两位数字的日期表示比较方便。但是会有一些问题,如2000年问题和2038年问题。Python程序会自动在00到68之间加上2000进行运算,而69到99之间的年份自动加上1900。也可以设置time.accept2dyear值为False,来禁止使用两位数的年份。如果设置了PYTHON2K环境变量,则time.accept2dyear值会初始化为Flase。


完成...






----------------------------------------------------------




第十四章 使用数据库

14.1使用基于磁盘的词典

Python标准库提供了一个简单的数据库,获取一个基于磁盘的词典(Disktionary)的格式。这个功能以UNIX的实用程序dbm为基础。

anydbm 可移植的数据库,从其他模块之间选择最佳模块

dumbdbm 速度慢而且有局限性,但可在所有平台使用

dbm 包装UNIX dbm实用程序,仅在UNIX上可用

gdbm 包装GNU的改进dbm,仅在UNIX上可用

dbhash 包装BSD数据库库,在UNIX和Windows上可用

通常建议使用anydbm。每个dbm模块都定义了一个dbm对象和一个名为error的异常。本书的描述适合于dbm的每一个版本。

open函数创建了一个新的dbm对象:open(filename[,flag[,mode]]) 。filename是文件名,flag参数可选,但是对dbhash来说是必须的,可用如下值:

r [默认]只读打开数据库

w 读写打开数据库

c 与w相同,但是必要时会自动创建数据库

n 与w相同,但总是创建一个新的空数据库

dbm中的某些版本,包括dumbdbm允许修改只读打开的数据库

mode为UNIX风格的许可权,用于设置数据库文件。

打开了数据库之后,就可以像正常词典一样的访问:

>>> SimpleDB=anydbm.open("test","c")

>>> SimpleDB['Terry']="Gilliam" # add a record

>>> print SimpleDB['Terry'] # 读取记录

Gilliam

>>> del SimpleDB['Terry'] # 删除记录

dbm中的键和值必须都是字符串,甚至不可以是数字。访问一个不存在的键会产生KeyError异常,可用has_key()提前检验,也可用keys()获得键列表。但是不可以使用词典中的安全get方法。

使用完成后使用他的close()方法写入到磁盘并释放资源。


14.2DBM示例:跟踪电话号码

import anydbm

import sys

def AddName(DB):

print "Enter a name.(Null name to cancel):"

# Take the [:-1] slice to remove the /n at the end

NewName=sys.stdin.readline()[:-1]

if(NewName==""): return

print "Enter a phone number."

PhoneNumber=sys.stdin.readline()[:-1]

DB[NewName]=PhoneNumber

def PrintList(DB):

for key in DB.keys():

print key,DB[key]

if __name__="__main__":

PhoneDB=dbhash.open("phone","c")

while(True):

print '/nEnter a name to look up/n+to add a name'

print '* for a full listing/n. to exit'

command=sys.stdin.readline()[:-1]

if command=="":

continue # 无事可做,再次显示提示

if command=="+":

AddName(PhoneDB)

elif command=="*":

PrintList(PhoneDB)

elif command==".":

break #quit

else:

try:

print PhoneDB[command]

except KeyError:

print "Name not found."

print "Saving and closing..."

PhoneDB.close()


14.3基于磁盘的高级词典

dbm的各个版本都不会使用兼容的文件格式。所有平台都兼容的格式仅限于dumbdbm。whichdb模块可以检测一个文件属于哪种数据库版本。whichdb.whichdb(filename)函数返回创建数据库文件的模块的名字。文件不可读或不存在时返回None,文件格式无法识别时返回空字符串。

>>> MysteryDB=anydbm.open('Unknown','c')

>>> MysteryDB.close()

>>> whichdb.whichdb('Unknown')

'dbhash'

14.3.1dbm

提供了一个附加的字符串变量library,是基本ndbm实现的名字。

14.3.2gdbm

提供了改进的键导航。dbm方法firstkey将返回数据库中的第一个键;之后nextkey(currentkey)将继续返回currentkey的下一个键。在完成了一些删除操作之后可用reorganize释放处已删除记录所占用空间。方法sync会把未写的更改刷新到磁盘上。

14.3.3dbhash

也提供了键导航。dbm方法first和last提供了第一个和最后一个键。方法next(currentkey)和previous(currentkey)分别返回下一个和上一个键。sync方法输出到磁盘。

当数据库很大时使用keys方法返回的键列表会占用大量内存。所以键导航方式性能会较好。注意处理KeyError。

14.3.4使用BSD数据库对象

bsddb模块即可在UNIX上使用,也可以在Windows上使用,提供了Berkeley DB库的访问。提供了散列表、B树、记录对象。树构造函数:hashopen、btopen、rnopen,与dbm构造函数使用的参数(filename,flag, mode)相同。该构造函数获取其他可选参数并直接传递给BSD代码,通常不应该使用。

BSD数据对象与dbm对象功能相同,也提供了一些附加的方法。方法first、last、next、previous都将导航并返回数据库中的记录。对于B树对象,这些记录是按照键值排序的;对于散列表和记录对象,并没有定义记录次序。此外,方法set_Location(keyvalue)会跳到keyvalue键的记录。

sync方法将会把数据写入文件。


14.4访问关系数据库

Python通过模块几乎可以访问任何数据库,如Oracle、MySQL、DB/2 、Sybase。Python数据库API为访问关系数据库的Python模块定义了标准界面。大多数第三方数据库模块近似地符合API,但并不是很完美。本章将介绍2.0版本的API。

14.4.1连接对象

connect()方法用于构造数据库连接。构造游标时将使用此连接。使用完连接后要用close方法释放。数据库通常提供一个有限的连接池。connect方法的参数将随着模块而改变,包含dsn(数据源名)、user、 password、host、database。

14.4.2事物处理

commit()连接方法可以提交当前事物处理;rollback()会取消当前事物处理。并非所有的数据库都支持事物。commit方法总是可用的,但rollback仅在支持的地方才可用。

14.4.3游标对象

游标可以执行SQL语句和检索数据。连接方法cursor创建并返回一个新游标。游标方法execute(command[,parameters])执行指定的SQL语句command,并传递必须的参数。执行之后的rowcount属性指明改变或返回的行数;description属性描述受影响的列数。执行完命令之后,方法fetchone()返回下一行数据(以序列的方式,每个列值都带有一个入口)。方法fetchmany([size])将返回行的序列-直到他们的size。方法fetchall()将返回所有行。

在使用游标之后用close方法来释放。数据库有一个有限的可用游标池,所以使用游标之后立即释放是很重要的。


14.5示例:"类似声音的"查询

下例使用mxODBC模块连接多种数据库。查询名字"听起来像"另一个人的名字。对每个名字进行一种编码,编码相同的名字就是相似的。

import ODBC.Windows

创建光标:

NewCursor=Conn.cursor()

执行SQL语句时放入try语句块,并提供finally来释放光标:

try:

NewCursor.execute('select ....')

finally:

NewCursor.close()

创建连接:

因为使用MySQL数据库不支持事物,所以设置一个选项,另外自己替换MyDB为自己的数据源名。

Conn=ODBC.Windows.Connect("MyDB",clear_auto_commit=0)


14.6检验相关的元数据

当游标返回数据时,游标属性description就是元数据-包含的列的定义。列的定义表示为一个7项的元组的序列。

0 列名

1 类型代码

2 显示大小(以列为单位)

3 内部大小(以字符或字节为单位)

4 数字的计数法

5 数字的精度

6 可空(如果为0,则不允许空)

例如一个例子:

>>> mc.execute('select FIRST_NAME,MANAGER_ID from EMPLOYE')

>>> mc.description

(('FIRST_NAME',12,None,None,5,0,0),('MANAGER_ID',3,None, None,1,0,1))

mxODBC模块并不返回显示大小和内部大小。


14.7示例:创建审计表

在更改原表的时候将原数据写入到一个镜像表中,提供数据的历史回溯功能。包括编辑时间,用户ID,数据等。而且一般将插入镜像表的SQL语句与原表编辑语句放入同一个事务中。

细节略...


14.8DB API的高级特征

各种数据库支持不同的类型,如INT和VARCHAR。数据库模块应该导出描述并用于description元数据。

>>> MyCursor.execute('select employee_name from employe where FIRST_NAME="Bob"')

>>> MyCursor.description[0]

('FIRST_NAME',12,None,None,3,0,0)

>>> MyCursor.description[0][1]==ODBC.Windows.VARCHAR

True

对某些具体类型如mxDateTime模块定义了具体的日期格式。

14.8.1输入和输出大小

游标属性arraysize指定了默认情况下每个fetchmany调用将返回多少行,默认值为1,可以增大。处理arraysize把大小传递给fetchmany会更有效。

游标方法setinputsizes(size)设置输入数据的内存限度,用以提高内存使用率。size是一个序列,每个项指定对应参数的最大长度,如果size的某个项为None则该列没有响应的参数值保存在内存里(默认)。

游标方法setoutputsizes(size[,columnindex])在读取庞大列(如LONG或BLOB)中的数据时设置了缓冲区的大小。未指定columnindex时给所有庞大列指定。

14.8.2可重新使用的SQL语句

就是带参数或者说预编译的SQL语句。下例:

>>> SQLQuery='select GAME_NAME from GAME where GAME_ID=?'

>>> MyCursor.execute(SQLQuery,(60,)) #赋予参数值60

>>> MyCursor.fetchall()

[('Air Combat 22',)]

>>> MyCursor.execute(SQLQuery,(200,))

[('Badlands',)]

参数标记的句法是由模块变量paramstyle描述的。游标句法:

executemany(command,parametersequence)多次运行同一个SQL语句,对于parametersequence中的每个参数集合,都运行一次。

14.8.3数据库的库信息

模块变量apilevel是一个字符串,说明支持的DB API层。应为1.0或2.0。如果不可用则为1.0。

模块变量threadsafety描述了模块支持哪一层并行访问:

0 线程不可以共享模块

1 线程可以共享模块

2 线程可以共享连接

3 线程可以共享游标

模块变量paramstyle说明了参数标记句法:

qmark WHERE NAME=?

numberic WHERE NAME=.1

named WHERE NAME=.name

format WHERE NAME=%s

pyformat WHERE NAME=%(name)s

14.8.4错误层次

数据库警告和错误是exceptions模块中StandardError类的子类。可以捕获Error类,完成常规错误处理。

最上层模块是exceptions.StandardError,之下的两个直接子类:Error和Warning。其中Warning没有子类。

Error的子类包括InterfaceError和DatabaseError。而InterfaceError没有子类。

DatabaseError的子类:NotSupportedError、OperationalError、 IntegrityError、ProgrammingError、DataError。

Warning 重大警告,如插入值时的数据截断

Error 错误基类,不直接产生

InterfaceError 数据库模块遇到内部错误,不是来自于数据库

DatabaseError 数据库本身的错误,多作为基类

DataError 无效数据错误,如超范围的数字

OperationalError 操作错误,如连接到数据库失败

IntegrityError 数据完整性错误

InternalError 内部数据库错误,如游标断开连接

ProgrammingError 对数据库模块的无效调用,如使用已关闭的游

标,或未查询时就先fetch

NotSupportedError DB API的某些部分是可选的,但是试图调用而

又没有可选的备用方案时发生


14.9小结

略...


完成...

你可能感兴趣的:(数据库,python,command,存储,import,compression)