[《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'

修饰符

字节顺序

对齐

大小

<

高序优先(little-endian)

标准

>!

低序优先(网络)

标准

=

内在的

标准

@(默认)

内在的

内在的

内在的

当使用了一个大小为"standard"的修饰符,则C short会占据2个字节,intlongfloat将使用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) #布尔型,取10

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

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

Packer对象还包含pack_bytespack_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

包含GZIPZIP文件的压缩支持。

最简单的用法为compress(str[,level]decompress(str [,wbits [,bufsize]])函数。压缩级别的level可用19,默认6,数值大则压缩率高而速度慢。解压缩用的wbits控制历史缓冲区大小,有815(默认)的数值,这个数值越大耗内存越多,但是会支持更好的压缩。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_FLUSHZ_SYNCH_FLUSH,会返回当前缓冲区的所有压缩数据,并且还可以继续添加压缩数据。如果忽略mode,则对象认为压缩完成,不需要添加数据了。

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

通过zlibdecompressobj([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 zipgunzip程序还支持其他多种格式,但是gzip模块只支持一种。

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

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

为了实现与普通文件相同的接口。可用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模块还定义了ZipFileZipInfoPyZipFile类。

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

压缩类型(storeddeflated)

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滴答

把时间中的一个点表示为"滴答"的次数,即新纪元后所经历的秒数。新纪元是任意选择的"时间开始"。对UNIXWindows来说,新纪元指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(6061是闰秒)

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,即通用协调时间。

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

gmtime的反函数是calendar.timegm


13.3解析及打印日期时间

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

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

13.3.1有趣的格式

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

代码

替换

示例/范围

%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指定了每一栏的宽度,最小值也是默认值是2linesperweek参数指定了每周打印多少行,默认值为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)

返回从firstyearlastyear之间的闰天数。


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程序会自动在0068之间加上2000进行运算,而6999之间的年份自动加上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数据库库,在UNIXWindows上可用

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

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

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

w 读写打开数据库

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

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

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

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

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

>>> 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的各个版本都不会使用兼容的文件格式。所有平台都兼容的格式仅限于dumbdbmwhichdb模块可以检测一个文件属于哪种数据库版本。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方法firstlast提供了第一个和最后一个键。方法next(currentkey)previous(currentkey)分别返回下一个和上一个键。sync方法输出到磁盘。

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

14.3.4使用BSD数据库对象

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

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

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


14.4访问关系数据库

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

14.4.1连接对象

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

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的高级特征

各种数据库支持不同的类型,如INTVARCHAR。数据库模块应该导出描述并用于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])在读取庞大列(LONGBLOB)中的数据时设置了缓冲区的大小。未指定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.02.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,之下的两个直接子类:ErrorWarning。其中Warning没有子类。

Error的子类包括InterfaceErrorDatabaseError。而InterfaceError没有子类。

DatabaseError的子类:NotSupportedErrorOperationalErrorIntegrityErrorProgrammingErrorDataError

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

Error 错误基类,不直接产生

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

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

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

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

IntegrityError 数据完整性错误

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

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

标,或未查询时就先fetch

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

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


14.9小结

...


完成...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值