gashero的mod_python开发经验

来源:互联网 发布:初级java工程师招聘 编辑:程序博客网 时间:2024/05/22 04:38

gashero的mod_python开发经验

 

·发布处理器的默认对象
    发布处理器可以在URI中没有明确指定模块名时默认指向到index.py模块。在index.py模块中寻找对应的方法,但是如果URI中除了目录名什么都没有则寻找对应的index.py模块中的def index(req[,...])进行处理。
    另外对于发布处理器使用的模块名可以不加扩展名。
    如果指定的URL中只有路径而没有指定具体模块,则会默认访问此路径下的index.py模块。同理,如果指定了模块名而没有指定哪个方法,就会默认的访问index方法。这样做是很有用的,推荐每个目录都保留index.py模块,并且在每个模块内部都保留index()方法。
    当不存在index.py而存在index.html等传统的默认对象时,不会自动链接到那种页面。而且.htaccess文件不允许指定DirectoryIndex标志来指定默认访问对象。
·模板发布
    将自己制作好的网页模板中按照规定位置加入Python词典的键名变量,用于替换。替换串规则如下:
    以百分号开头,然后是小括号中为词典键名,其后是类型标志。如下将会被词典中键名为name的对替换为字符串值:
    %(name)s
    这样生成好了相应的替换网页,之后是在处理器中生成对应的字典来替换。先读取对应文件,然后将文件当作一个字符串来全部读入之后进行变量替换,然后将页面返回到客户端。
    模板文件的位置,以apache的安装目录为基准路径,比如我的一个完整的处理器例子:
def get(req):
    ff=open("htdocs/ns/index.pyml","r")
    allfile=ff.read(65536)
    dicts={'name':'Gashero',}
    return (allfile % dicts)
    #by gashero
    每一个模板中存在的需要替换的变量必须进行替换,否则发生服务器内部错误,日志为发布处理器抛出的KeyError异常。而如果词典中有剩余的键值对在模板中没有用到则无所谓,不会出现任何错误。
    如下是用于CommonTest项目中的一个较为成熟的模板函数。
def get_page(path,filename,vardict):
 """get a dynamic page from a file

 and will fix the var with dict, then return page string
 and status"""
 if (type(path)!=types.StringType) | (type(filename)!=types.StringType) | (type(vardict)!=types.DictType):
  apache.log_error('[pytemp]: args type error')
  raise apache.SERVER_RETURN,apache.HTTP_INTERNAL_SERVER_ERROR
 fullpath=os.path.join(path,filename)
 try:
  file=open(fullpath,'r')
  filestr=file.read()
  file.close()
 except:
  apache.log_error("[pytemp]: can't open & read file")
  raise apache.SERVER_RETURN,apache.HTTP_NOT_FOUND
 try: #by gashero
  filestr=filestr % vardict
 except KeyError,keyname:
  apache.log_error('[pytemp]: vardict not match KEY='+keyname)
  raise apache.SERVER_RETURN,apache.HTTP_INTERNAL_SERVER_ERROR
 return filestr
    其中第一个参数path是模板文件所在路径,第二个参数filename是模板文件名,第三个参数是用于在模板中用于变量替换的替换词典。这样每次调用get_page()函数就可以返回相应的替换过后的模板HTML文件字符串。当模板中的键在词典中不存在时的问题在这个模板函数中也做了相应的处理,将会返回给用户一个服务器内部错误页面。模板文件打开的过程可能遇到的种种错误也都做了相应的处理,并写入了Apache服务器日志做记录。
    这个模板函数提供了path和filename分开的方式是为了比较方便的支持相对路径下的模板文件。用于获取URL中当前指定路径的函数如下,可以用于填充path,即使用URL中指定的当前路径下的模板文件。
def get_cwd(req):
 if req.__class__.__name__!='mp_request':
  return None  # a invoke error
 path,fn=os.path.split(req.filename)
 return path
    这样处理之后加上对应的导入模板,构成一个专用的模板模块,用于在mod_python中提供模板功能。如下是调用示例:
retstr=pytemp.get_page(pytemp.get_cwd(req),'queslist.html',vardict)
return retstr
    代码截取自queslist.py模块,在调用这个语句之前先要将模板中所有需要用到的变量填入vardict词典中。
·发布处理器的无用参数
    当使用发布处理器而URI中客户提交了用不到的参数,比如对应处理函数的参数中没有对应的参数时,则这个提交的字段直接丢弃,如果确实需要处理这种无理取闹的提交字段则可以用字典来接收。在编写这种处理器函数时也要按照如下形式:
def myHandler(req,**kargs):
    这样就可以通过kargs来获取所有的输入参数。但是一般来说每一个处理器函数都是为了处理特定事件而存在的,所以一般的处理器函数只是如下形式:
def myHandler(req,aaa=***,bbb=***[,...]):
    这样就可以只接收特定的参数,抛弃不必要的麻烦,而且对于客户端再次无理取闹不提交一些参数时,也有缺省值可供选择。
    如果确实需要查看所有的输入参数,则一方面可以用词典来接收未定义的提交字段,另外可以用str(词典)来将词典转换为字符串显示出来,便于调试。
    经过URI提交的多个字段之间与主PATH通过问号分隔,多个字段之间通过分号分隔。
·通过mod_python发布静态页面
    默认时通过在.htaccess中设置SetHandler mod_python是无法发布HTML页面的,如果确实需要可以在下面加上如下一条即可:
AddHandler mod_python .html
    但是注意,现在发现的可支持类型也仅限于.html,不包括其他类型。通过这样设置之后一个较为完整的.htaccess文件的内容如下:
SetHandler mod_python
AddHandler mod_python .py
AddHandler mod_python .html
PythonHandler mod_python.publisher
PythonDebug On
    这个配置可以允许在URL中调用模块时不加.py的后缀名,但是加上也是允许的。支持显示静态HTML文件,允许调试。
    注意此经验仅适用于mod_python3.2.5b,当升级到mod_python3.2.8时不再有效。但是使用mod_python3.2.8时,发布处理器publisher是无法直接提供HTML静态文件的显示。只好用如下配置来设置publisher的运行,并使用模板系统和其他文件夹下的静态HTML文件来实现显示:
SetHandler mod_python
PythonHandler mod_python.publisher
PythonDebug On
    但是如上的不可以分拆开来书写,也就是如下:
SetHandler mod_python.publisher
PythonDebug On
    的效果是不同的,这时处理器不会运行,直接返回python的源码。
·发布处理器的对象内部验证
    发布处理器支持在对象内部提供三个成员来验证访问权限,三个成员分别为__auth__、__access__、__auth_realm__。三个成员分别提供了登录验证,用户名验证和验证提示。注意,虽然mod_python号称允许设置__auth__为一个词典,即可直接验证用户名,但是实际测试表明所有的输入都是允许通过的。所以,真正的验证需要使用__auth__作为一个函数来调用。
    我比较偏向与把__auth__作为一个函数内部的函数,这样可以避免官方文档中提出的可能在某些模块未加载时的绕过验证。但是注意,如果需要在__auth__这个内部函数中调用同一个模块的函数时,仅仅靠global来引入函数名是无效的。还需要在__auth__函数内部再次导入验证函数所在模块名。推荐把这个验证函数做好一些,直接放入数据库访问模块里面去。
    我的一个例子,注意验证方式,和导入:
# inauth.py
def index(req):
    def __auth__(req,username,password):
        import inauth #这里要重新导入 by gashero
        return inauth.myauth(req,username,password)
    __auth_realm__="登录验证"
    return "Auth OK!"

def myauth(req,username,password):
    if username=='aaa' and password=='aaa':
        return True
    else:
        return False
    当然,注意__auth__函数的参数,是必须这样的,不可以更改。
·一个连接MySQL数据库进行用户验证的例子
    注意只是一个简单的例子,可能存在SQL注入漏洞:
import MySQLdb
con=MySQLdb.connect(host='localhost',port=3306,/
    user='***',passwd='***',db='***')
cur=con.cursor()
sql="SELECT username,name FROM login WHERE/
    username='%s' AND password='%s';"
preSQL=sql % (username,password)
rt=cur.execute(preSQL)
results=cur.fetchall()
con.close()
if rt==1:
    print 'auth ok!'
else:
    #by gashero
    print 'auth failed!'
·mod_python的WEB应用程序中的命令行参数
    Python的命令行参数通过sys.argv来获取,在mod_python的WEB应用程序中,命令行参数只有一个是'mod_python',没有其他参数。
·一个Session编程的例子
    mod_python中默认实现的Session很有意思。对不同的浏览器进程会有不同的Session同时存在,互不干扰,但是对于同一个浏览器进程的不同窗口,实现的Session是相同的。默认的Session超时时间为半个小时。Session的操作方法与词典相同,非常方便。
from mod_python import Session
def index(req,oper=''):
    sess=Session.Session(req)
    #sess.set_timeout(90)
    if oper=='delete':
        sess.invalidate() #删除Session
        return 'Session deleted!'
    if sess.is_new():
        sess['name']='gashero'
        sess.save() #需要保存一下
        return "New Session %s" % (sess.id())
    else:
        retstr="Name=%s /n" % sess['name']
        retstr+="SessionID=%s" % sess.id()
        return retstr
·关于mod_python运行中模块的__name__属性
    这个属性在运行时并不是变成普通的模块名,而是变成带有完整路径加上模块名的样子,不要依赖它。
·PythonOption标志
    可以在.htaccess或httpd.conf中指定PythonOption标志,语法如下:
PythonOption name value
    这样可以向一个已有的httpd表格对象中添加键值对。通过req.get_options()获得这个表格的引用并从中提取这些设置参数,可以用于给自己的程序添加选项。尤其是.htaccess中,只要设置有所更新,立即可以读取,非常方便。但是由于获得这个表格对象一定要使用req对象,所以难以用于程序的初始化配置等工作。另外也是由于必须使用req对象,而req对象的生命周期是请求,所以即使先存储了由req获得的mp_table对象,请求结束后这个对象也会被删除。这样如果mod_python内部没有对配置文件使用缓存则每次读取文件都耗费大量时间,如果使用了缓存又无法实时更新配置。by gashero
    总之用起来很不爽就是了,后来我重新写了一个模块专用于读取M$ ini格式的配置文件,并将配置保存到字典中。
·关于mod_python.Session
    在win32上默认实现为MemorySession,所以在服务器重启之后Session就消失了。另外注意,创建Session时最好指定timeout值,可以设的长一点,默认为1800,单位是秒。如果没有设置timeout值,偶尔会出现刚刚设置的Session就突然丢失的情况。具体出现原因至今尚未查明,也可能与我的浏览器有关,但是推荐还是先设置好。
    另外为了防止出现为止的错误,最好还是要在创建Session之后检查一下是否是新创建的Session,如果不是则load()一下之前的数据。如上段的奇怪错误就是这样最终解决的。推荐每一个需要使用到Session的代码段按照如下书写:
from mod_python import Session
sess=Session.Session(req,timeout=3600)  #1小时
if not sess.is_new():
    sess.load()
# other operation ...
    另外,由于HTTP是无状态协议,仅靠Session和Cookie来维持交互状态,所以偶尔会出现一些混乱的顺序破坏服务器状态,比如一个函数需要在Session中存在了某个对象之后才可以进行操作,而客户端偏偏在此前先预存了这个函数对应页面的URL,而直接访问这个页面,导致服务器的错误。所以推荐读取Session中某个对象之前按照如下操作,防止意外的错误:
if sess.has_key('key_name'):
    obj=sess('key_name')
else:
    apache.log_error('[*][*] ...')
    raise apache.SERVER_RETURN,apache.HTTP_INTERNAL_SERVER_ERROR
    另外就是每次操作Session结束之后需要用sess.save()方法提交更改到对应的存储介质。
·发布方法的参数
    推荐使用如下方法声明发布方法:
def func(req,a=None[,b=None...])
    这样可以在客户端提交数据不足时有所察觉,如果客户端含有对应名称的字段,而这个字段没有赋予值时,是返回空字符串'',而如果页面被恶意修改,则提交时则对应字段为None。而在页面被恶意修改时可以用日志输出一些东西来记录客户端的攻击行为。
·发布方法的返回值
    发布方法的返回值就是字符串,虽然也可以返回其他类型,并在内部被用str()函数取得对应字符串表示,但是还是推荐使用字符串进行返回,可以方便的控制返回内容。一个推荐的方法是在方法开头定义需要返回的字符串变量,如:
retstr=str()
    这样变量retstr被初始化为'',后面就可以用retstr+=*来进行多种串接操作。
·导入语句的位置
    按照惯例,import语句放在模块文件头,"# -*- coding: gbk -*-"等编码声明之后,但是对于某些具有特别的作用域的方法,如方法内部的认证方法等,是无法共享这个导入设置的。但是也有解决办法,就是可以在方法的内部使用导入方法,格式相同。
·apache-httpd的日志输出
    最简单的日志输出方法就是按照如下:
from mod_python import apache
apache.log_error(str)
    但是推荐不要在错误输出的信息中使用中文,否则会因为各种编码的混乱问题导致日志无法读取。
    因为WEB开发一般只能通过页面进行输出,把Python的交互特性损失殆尽,所以只能使用日志作为另一种较好的输出方式,以方便调试。推荐的输出字符串格式如下:
'[module][func] str'
    这样可以指出对应的模块和相应的函数,很方便于调试,但最重要的是要形成一定的日志风格,否则以后很难理解。
    一般在写入日志的时候都是发生了很严重的错误,或是检测到攻击行为。所以很多时候都是在输出日志之后加上抛出错误的语句,如服务器内部错误:
raise apache.SERVER_RETURN,apache.HTTP_INTERNAL_SERVER_ERROR
·用于mod_python的自定义配置读取模块
    由于mod_python中使用req.get_options()读取配置的不方便,所以自己写了这个模块,可以比较简陋的支持M$ ini配置文件格式。暂时不支持段,对于同名的配置标志,后面的会覆盖前面的。可以自动忽略以";"开头的注释,自动忽略空行,自动忽略没有"="的行。只能识别第一个"="两端的键值对,在第二个"="之后的部分无法识别。键和值的字符串两端的空格会被去掉。最终的配置结果保存在一个字典当中。代码如下:
# -*- coding: gbk -*-
# Date: 2006-04-22
# Author: gasheor
#
Copyright@1999-2006, Harry Gashero Liu.
from string import split
from string import strip
def getconfig(filename):
    global ctconfig
    ctfile=open(filename)
    if ctconfig=={} and ctfile!=None:
        while True:
            line=ctfile.readline()
            if line=='':  #文件结束
                break
            line=strip(line) #去除头尾空格
            if line!='' and line[0]==';': #去除注释行
                continue
            if '=' in line:  #含有配置的行
                retstr=split(line,'=')
                if len(retstr)>=2:
                    key=retstr[0]
                    value=retstr[1]
                key=strip(key)
                # by gashero
                value=strip(value)
                #print "'"+key+"'='"+value+"'"
                ctconfig[key]=value
            else:
                #有错误的行存在
                pass
    ctfile.close()
    return ctconfig
######### variant ###########
#存储的文件配置的词典
ctconfig=dict()
    用于测试的代码如下,可以加在如上模块当中:
if __name__=="__main__":
    myconf=getconfig('commontest.ini')
    print str(myconf)
    如下是一份配置文件,是完全可以使用如上模块读取的:
;注意此配置文件所包含的各个字段不要删除,
;否则会影响系统的正常运行。
;注释行以英文分号开头";"
;通用考试系统作者,测试用
Author = gashero

;考试持续时间长度,以分钟为单位
;例如"timelong=120"是两个小时的考试
timelong=5

;登录数据库位置,最好不要修改
logindb=htdocs/CT/scripts/students.db

;考试结果数据库位置,最好不要修改
resultdb=htdocs/CT/scripts/results.db
    当然格式过于单调,可以参考模块代码来使用可以理解但是更古怪的配置信息。
·mod_python中全局服务器设置
    有时需要使用一些具有Application作用域的变量,可是mod_python却没有提供,笔者也试用过在创建Session时手动指定sid,来在各个请求之间共享同一个Session,但是总是设置无效,mod_python还是自己生成sid。但是经过考察python的实现原理时发现,可以利用一些非mod_python的特性来存储Application作用域的变量,就是使用模块变量,当然也可以考虑使用类成员(不是实例成员)。这样可以在如上的读取配置模块中加入一个词典来提供Application作用域变量的支持,如下:
#存储运行时配置的词典,随便用
server=dict()
    经过了实际的测试还是非常好用的。
    Application作用域变量一方面可以用来存储一些全局配置,还可以用于页面缓存,把一些经常访问的文件全部缓存到内存中,甚至一些参数很少改变的动态页面也可以缓存来提高服务器性能,当然这样作是要牺牲一定的内存的。
·字节码方式发布mod_python应用
    以.py方式的源码发布WEB应用多少有些风险,感谢python的先进特性,我们可以以字节码的方式发布WEB应用。首先需要备份整个WEB应用,然后将每个模块编译为.pyc的字节码文件,然后删除.py源文件即可。
    编译方式如下,可以写一个小脚本用于编译单个脚本或整个目录。如下:
·关于模块被mod_python导入的不同
    这里有一个小细节,就是python模块之间的导入与被mod_python导入是不同的。哪怕是同一个mod_python程序中,一个模块import另一个模块也是一种标准的导入行为,而被mod_python导入,则是不标准的。
    mod_python的导入不会执行模块级别的代码,也就是模块初始化代码,而只是从模块文件中提取它自己需要的对象而已,比如类实例、函数等。
·mod_python的系统设置
    所有的mod_python应用中各个模块本该有的一些配置被替换成了mod_python设置。
    比如模块的__name__属性被替换为当前OS下模块的完整路径的文件名,其中的特殊字符被替换成下划线,如"D_httpd2_0_55_Apache2_htdocs_CT_scripts_test_py"。而一般的应用程序命令行参数sys.argv是一个列表,只有一个空字符串作为元素,在mod_python中这个列表长度也是1,元素是"mod_python"。
    因为mod_python应用还是要嵌入在apache-httpd中,所以使用的当前路径就是apache-httpd的安装路径。比如一个相对路径就是'htdocs/CT/index.html'。相对路径要由apache-httpd的安装目录开始,而不是httpd进程的启动目录。
·初始化一些变量的方法
    可以用赋予一个简单的初值或者使用对应对象的构造方法,使用构造方法看起来更朴实一些。比如初始化一个空字符串,常用如下:
a=''
    也可以用构造方法来进行:
a=str()
# by gashero
    同理还可以用其他的构造方法,比如字典dict()、列表list()等。
·from导入行为和去除字符串边缘空格
    使用from package import obj导入的对象是加入到当前模块空间的,从此属于当前模块的一部分了,通过在外部使用dir()等方法都是可以看到的,也可以通过当前模块间接调用原方法。比如为了使用字符串的分隔和去除边缘空格函数,使用了如下的导入方法:
from string import split
from string import strip
    从此之后可以在当前模块的任何地方象使用全局函数一样使用这两个函数,并且其他模块也可以通过本模块来调用这两个函数。
·__doc__的自动缩进
    推荐的使用__doc__方法是使用三重引号,如:
 """a

 bbb"""
    这样的a在实际显示时就是左对齐的顶头显示,而bbb则是比a缩进了一个TAB来显示的。实际上不管如何bbb都是要比a缩进一个TAB,无论a的下面是否有一个空行。这个缩进是明确的只有一个TAB的大小,无论实际上书写时bbb前面有多少空格或者多少TAB。另外按照惯例,还是在"""的开头就写一下简介,然后是空行,然后是详细的注释介绍。
·python对中文的支持
    笔者使用的Python 2.4.3已经对中文有了很好的支持了,支持多种编码方法和编码的别名。因为笔者常用紫光拼音,所以平时使用的文件编码声明为:
# -*- coding: gbk -*-
    如果使用编码为'chinese'则为gb2312的别名。
·Python中的类变量和实例变量
    Python中的类变量是在类定义的级别进行定义的变量,各个类的实例共享这些变量的值,可以用于在各个实例之间传递一些共同参数等,用处有限。但是注意实例变量一定要在方法内部使用self.前缀来引用。Python中不可以在方法之外定义实例变量,所以一般情况是在构造方法__init__()中定义,并初始化。所有的实例变量在__init__()中统一声明有助于编写清晰的代码,尽管Python使用一个变量之前并不需要声明。另外,虽然可以在运行时给类添加成员变量,即运行时修改类定义,但是并不推荐这种做法,不利于代码的可读性。
    类的所有方法第一个参数都需要使用self,尽管可以定义没有任何参数的方法,但是运行时会出错。所有的方法在被调用时都会传递实例本身的引用作为第一个参数,如果第一个参数不用self,可能会使你的代码出错。而self只是个推荐的命名惯例,可以不用这个名字,但是会导致可读性降低。
    推荐所有的方法或者函数的最后加上一条return,即便是空的return,这样可以防止代码在该停止的地方因为格式的问题而继续执行下去。
·对象持久化pickle
    对于对象持久化,最好不要让对象的成员是另一个巨大的对象,这样在持久化时很不好处理。而推荐只是对方的索引即可。比如通用考试系统中Student类的paper成员是不需要存储的,所以可以使用对象。而Paper类的也需要存储用户信息,但是只用了一个字符串字段来存储用户名作为索引即可。这样可以极大的减小存储空间要求。
    对象的持久化非常推荐在类定义时就定义相应的__getstate__和__setstate__方法,来仔细的控制对象的存储和读出。还可以在__setstate__中作一些类似构造函数的事情来初始化那些没有存储的成员。
    还有就是测试时注意,测试时一般的__name__为__main__,这个作为模块名是要被pickle读取并写入存储对象的。而再次读取的时候也需要验证这个对象所属的模块和类定义,所以测试时写入的数据如果在__name__=='__main__'时,则再次读取数据就会出错。因为可能那时的__name__就使用了真正的模块名。
    很多对象不仅仅要存储,有时还需要显示摘要,这就需要实现__str__(self)方法。通过这个方法返回一个字符串,在str(obj)时就可以自动调用这个方法来返回对象的信息摘要了。
    一个__getstate__和__setstate__的例子:
class Paper:
    ... ...
    def __getstate__(self):
        return (self.username,self.answer)
    def __setstate__(self,state):
        #一些实例初始化工作
        self.username,self.answer=state #解包
        return
·使用dbhash数据库
    尽管dbhash数据库的查询功能远不及现有的RDBMS,但是其后端数据库BerkeleyDB也是四大开源数据库之一,稳定性不同凡响。另外它使用的key:value对方式也是非常适用于以对象作为单位进行存储的应用。存储方式也非常开放,不需要什么特定的格式就可以按照键值对方式存储。
    另一个特点是存储速度,由于是非RDBMS,而且技术相当的成熟,经过了数十年的发展,在先进的存取速度可以超越所有的RDBMS。
    安全性方面,由于并不使用SQL语句来控制存取,而是自己手动打包和解包数据,所以安全性非常好,至少是没有SQL注入漏洞。
    dbhash适用于一些较为简单或者非常灵活的应用,存储方式不再以表格为单位,而是以用键来索引的对象为单位。比如可以用于用户验证,以用户名作为键名,用户的完整数据作为键值,进行验证。甚至可以提高速度,直接使用用户名和密码作为键值对存储进去。而对用户信息的提取可以在通过验证之后再使用其他dbhash数据文件进行提取。
    由于dbhash的实现是具有内部缓存的,所以推荐还是使用一个DB连接池进行存取操作,这样会进一步提高系统性能。因为现在的应用系统主要瓶颈来自于数据库的查询,所以这种简单的查询方式和超高的效率还是有一定的用武之地的。
    关于dbhash的打开。默认的只有数据文件名的打开方式是只读的数据库,不可以修改。另外出于考虑到有时数据库文件可能尚未创建,所以一般指定flag为'c',这样就可以兼顾数据文件不存在和可以写入两个方面了。如下:
import dbhash
db=dbhash.open('filename.db','c')
    这样就可以按照词典的方式来使用db对象了,处理过程中可以用db.sync()方法来实时写入数据到磁盘来保存。还可用db.close()来将数据存入磁盘并关闭数据文件。
    一般来说读取对应键之前可以用db.has_key(key)来判断一下是否有对应键存在,否则会抛出KeyError。当然也可以用db.get(key,default_value)来在键不存在时获取默认值。因为这时的db具有词典的接口,所以可以使用db.setdefault(key,default_value)来读取值的同时将键值对存入数据文件。
    可以自己选择如上的哪种方法来读取数据,has_key(key)方式需要查询键两次,所以效率会低一点,并不是太推荐。可以自选使用get()或setdefault(),当然这都是词典的方法。
    对于dbhash数据文件,创建之后自动使用24KB(24,576bytes)的磁盘空间,数据量扩大之后再继续扩大。但是如果存过大量的数据之后,即便删除了那些数据,数据文件的大小也不会再缩小,所以可以自己手动定期整理数据文件,防止占用过大的空间。尽管数据文件会扩大,但是删除多余数据之后,曾经的空间还是可以被dbhash再次利用的,而不存在'磁盘泄漏'。
    在编写应用系统时推荐把如上的dbhash读写功能和对象的打包和解包功能全部做到一个模块当中,提供对象的数据库读写接口。当然还是要注意同一个问题就是小心对象打包时当前的__name__属性的值不要为__main__,防止以后无法读取。一种推荐的接口包括三个函数如下:
def getdb():
    "用于获取数据库对象"
    pass
def get_obj(key):
    "按照键获取值对象,不存在则返回None或抛出错误,内含解包"
    pass
def set_obj(key,obj):
    "将键值对对象存入数据库,内含打包操作"
    pass
    具体应用中可能涉及不只一个数据文件,所以可以自己更改接口的命名。我上一个系统设计的一塌糊涂,将如上三个函数合并为一个 了,如:
def getdb(key=None,obj=None):
    pass
    当key和obj都为None时就返回数据库对象,当key!=None and obj==None时返回值对象,当key!=None and obj!=None时将键值对存入数据库。
·关于index.py
    推荐还是写一个index.py页面脚本,因为谁也不知道未来的mod_python会添加什么奇怪的功能进来。所以有个index.py页面可以定制用户访问特定目录时行为,哪怕这个页面什么都不作,只有一个index函数即可。
    我的index.py页面内容如下,只是为了防止低版本的mod_python在apache-httpd访问特定目录时返回目录的文件列表,所以抛出一个404错误。代码如下:
from mod_python import apache
def index(req):
    raise apache.SERVER_RETURN,apache.HTTP_NOT_FOUND
    return 'gashero'
    当然,我是使用mod_python.publisher进行开发的,其他开发方式不受这个限制。
·时间操作小析
    Python中的时间操作还是比较方便的,有专门的time模块提供多种接口形式。其中常用的两种操作数据格式就是滴答和时间元组,关于这两个奇怪的名字,我也不便解释,总之在一本书上看着这么写,也就这么用,确实很难听。滴答是从1970年开始到现在的秒数,在Python2.4.3中是一个浮点数,小数部分为更精密的部分,小于1秒的精细时间值。时间元组是一个具有9个成员的元组,依次为:年、月、日、小时、分钟、秒、星期(0-6,0为周一)、一年中的哪一天(1-366,古罗马历)、Daylight savings(-1,0,1,我也不知是干什么用的)。
    所以可以用如下方法提取当天的当时时间:
a,a,a,hour,minute,second,a,a,a=time.localtime(time.time())
    这里的hour、minute、second分别是当时的时、分、秒,也是比较常用的参数。另外就是关于求时间差,如果使用time.localtime()测定时间差,那么滴答值为0时的中国标准时间是1970年1月1日8:00,时间差无从谈起,所以对于时间差应该使用time.gmtime()转换为TimeTuple再解包为各个时间参量。比如如下获得时间差:
a,a,a,hour,minute,second,a,a,a=time.gmtime(t1-t2)
·mod_python中的外部重定向操作
    为了在一个操作之后转向需要显示的路径,需要使用重定向操作。内部重定向改变返回的内容到指定的路径,但是客户端的显示路径还是当前路径。这样当再次在客户端点击某些链接时,使用相对路径就会很麻烦,往往导致难以调试的问题。而外部重定向使用了HTTP报头发送相关的重定向信息,要求客户端自己更改当前路径,并取得对应路径的页面。这样可以使得相对路径仍然好用。比如如下模块:
def index(req):
    return "...."

def modify(req):
    假设此模块名为test.py,则打开此模块的URL为:"
http://localhost/CT/test",也就是打开test模块的index函数。如果index函数返回的HTML代码中包含一个表单,此表单提交到modify函数,则表单的action为"modify"即可,使用相对路径。这样提交表单之后的客户端URL变为"http://localhost/CT/test/modify",显示的页面为modify函数的返回值。但是一般来说并不实用modify函数的返回值,而是仍然是用index函数,来显示操作后的刷新结果。如果只是内部重定向,改变modify函数的返回值而已,则再次提交此表单时的绝对action路径就成了"http://localhost/CT/test/modify/modify",显然是错误的。所以在这种情况下就需要使用mod_python的外部重定向。
    mod_python中的外部重定向使用util模块的redirect方法。此函数需要两个参数,第一个参数为请求对象,第二个参数为重定向路径。其中重定向路径允许使用相对路径。调用方法如下:
from mod_python import util
def modify(req):
    #... ...
    util.redirect(req,"newURL/gashero")
    return None
    这样就可以实现乐重定向操作。但是一般来说,为了提高系统的可移植性,使用相对的URL操作。如果newURL使用了".",则如上例的路径由"
http://localhost/CT"变为"http://localhost/CT/",注意末尾多了一个"/"。就是这个"/"导致相对路径的错误,还是无法使用。所以如上例中的modify方法推荐的重定向路径为:
util.redirect(req,"../test")
    注意其中的test为modify所在的模块名。
    在util.redirect()函数的内部实现当中会抛出一个异常来结束当前函数的执行,所以在编写的函数中util.redirect()函数之后的所有语句都不会执行,包括return语句。如果确实非常需要在外部重定向之后做一些工作,可以将util.redirect()函数放在try语句块中,处理其抛出的异常。
    同为Python的WEB开发技术的CherryPy所有的重定向都是使用关于跟路径的相对路径,使用比mod_python方便很多,也不需要太多的考虑相对路径的相对关系。

原创粉丝点击