gashero的mod_python開發經驗

來源:互聯網
上載者:User
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...])
    這樣可以在用戶端提交資料不足時有所察覺,如果用戶端含有對應名稱的欄位,而這個欄位沒有賦予值時,是返回Null 字元串'',而如果頁面被惡意修改,則提交時則對應欄位為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是一個列表,只有一個Null 字元串作為元素,在mod_python中這個列表長度也是1,元素是"mod_python"。
    因為mod_python應用還是要嵌入在apache-httpd中,所以使用的當前路徑就是apache-httpd的安裝路徑。比如一個相對路徑就是'htdocs/CT/index.html'。相對路徑要由apache-httpd的安裝目錄開始,而不是httpd進程的啟動目錄。
·初始化一些變數的方法
    可以用賦予一個簡單的初值或者使用對應對象的構造方法,使用構造方法看起來更樸實一些。比如初始化一個Null 字元串,常用如下:
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方便很多,也不需要太多的考慮相對路徑的相對關係。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.