讓我們描繪一下本文的情節(jié):假設您要在本地機器上運行一個進程,而部分程序邏輯卻在另一處。讓我們特別假設這個程序邏輯會不時更新, 而您運行進程時,希望使用最新的程序邏輯。有許多方法可以滿足剛提到的要求;本文將向您說明其中幾種方法。
隨著“可愛的 python”專欄不斷進行,已經(jīng)討論了我的公共域?qū)嵱贸绦?txt2html 的正在進行的增強。該實用程序?qū)ⅰ爸悄?ascii”文本文件轉(zhuǎn)換成 html。以前的文章討論了實用程序的 web 代理版本和實用程序的 curses 界面。同樣,我偶爾注意到可以用更有效的方法轉(zhuǎn)換某些 ascii 標記,或者解決了一個在處理某個特殊標記結(jié)構(gòu)中的錯誤。
事實上,本專欄的文章都是用 ascii 編寫的,然后在編輯過程中轉(zhuǎn)換成您可以閱讀的 html 格式。在發(fā)表文章草稿之前,我運行了類似以下處理的程序:
文章的命令行 html 化
?1 txt2html charming_python_7.txt > charming_python_7.html
如果愿意,我可以指定一些標志來修改操作;但不管怎樣,事實上轉(zhuǎn)換器的最新版本在我的本地驅(qū)動器和路徑中。如果在另一臺機器上工作,或者對于要使用該實用程序的讀者,則過程比較麻煩:請訪問我的網(wǎng)站,注意比較版本號和文件日期(有時更改太小,我不會更改版本號),下載當前版本、將當前版本復制到正確目錄,然后運行命令行轉(zhuǎn)換器。(請參閱本文后面的 參考資料。)
以上的過程包括幾個需要手工操作且比較費時的步驟。應該更簡單,而且可以做到這點。
命令行 web 訪問
大多數(shù)人認為 web 是在 gui 環(huán)境中交互式瀏覽頁面的一種方法。那樣做當然很好,但命令行中也有許多功能。帶文本模式 web 瀏覽器 lynx 的系統(tǒng)完全可以將整個 web 看作是命令行工具使用的另一個文件集。例如,我發(fā)現(xiàn)有些命令很有用:
使用 lynx 進行命令行 web 瀏覽
?123 lynx -dump . lynx -dump . > ibm_developer.txt lynx -dump | wc | sed s/( *[0-9]* *\)\([0-9]*\)\(.*\)/\2/g
第一行說:“將 david mertz 的主頁(以 ascii 文本)顯示到控制臺?!钡诙姓f:“將 ibm 的當前 developerworks 主頁的 ascii 版本保存到文件?!钡谌惺纠f:“顯示 david 主頁的字數(shù)。”(不必擔心細節(jié),它只顯示與管道結(jié)合的命令行工具。)
關(guān)于 lynx,有一點要注意它(使用 -dump 選項時)執(zhí)行幾乎與 txt2html 完全相反的操作:前一種工具將 html 轉(zhuǎn)換成文本;而后一種工具則轉(zhuǎn)換成其它格式。但沒有理由不使用與 lynx 一樣流行的 txt2html??梢允褂靡粋€很短的 python 腳本完成這個操作:
'fetch_txt2html.py' 命令行轉(zhuǎn)換器
?12345678 import sys from urllib import urlopen, urlencode if len(sys.argv) == 2: cgi = 'http://gnosis.cx/cgi/txt2html.cgi' opts = urlencode({'source':sys.argv[1], 'proxy':'none'}) print urlopen(cgi, opts).read() else: print please specify url for txt2html conversion
要運行這個腳本,只要執(zhí)行如下操作:
?1 python fetch_txt2html.py
這并沒有向您提供本地 txt2html 處理的全部開關(guān),但如有必要,添加它們也很容易??梢韵袷褂萌魏蚊钚泄ぞ咭粯觼磔斔秃椭囟ㄏ蜉敵?。但是,在上述版本中,只能處理 url 可以到達的數(shù)據(jù)文件,而不能處理本地文件。
實際上, fetch_txt2html.py 可以完成 lynx 不能完成的任務(txt2html 本身也不能):它不僅從 url 取得數(shù)據(jù)源,而且還遠程獲取 程序邏輯 。如果使用 fetch_txt2html.py ,就 不必在本地機器上安裝 txt2html;將(使用最新版本)遠程調(diào)用處理,并且將把結(jié)果發(fā)送回來,就像運行的是本地進程。很棒吧?txt2html 的本地版本可以訪問遠程 url,就像訪問本地文件一樣,但它還不能保證它自身是最新的……!
動態(tài)初始化
使用 fetch_txt2html.py 確保了在轉(zhuǎn)換中始終使用最新的程序邏輯。但是,這個方法可以完成的另一件事情是將處理器(和內(nèi)存)的需求轉(zhuǎn)移給 gnosis.cx web 服務器。此特殊進程的負載并不是特別高,但人們卻很可能認為在客戶機上處理的其它類型的進程會更有效且令人滿意。
組織 txt2html 的方式 -- 也就是組織大多數(shù)程序的方式 -- 是用一些由各種實用函數(shù)提供的核心流量控制函數(shù)。尤其是這些實用函數(shù)是一些經(jīng)常更新的函數(shù);核心函數(shù)( main() 和一些其它函數(shù))只有在做重大改寫時才會變動??偠灾诿總€程序運行時有效更新的就是實用函數(shù)。其實,大部分情況下,主 txt2html 模塊 dmtxt2html 中的大多數(shù)函數(shù)就夠了。
'd2h_textfuncs.py' 動態(tài) txt2html 更新
?12345678910111213141516171819202122232425262728293031 hot-pluggable replacement functions for txt2html #-- functions to massage blocks by type #def titleify(block): #def authorify(block): # ... [more block massaging functions] ... #-- utility functions for text transformation #def adjustcaps(txt): #def capwords(txt): #def urlify(txt): def typographify (txt): # [module] names r = re.compile(r '([\(\s'/>]|^)\[(.*?)\]([<\s\.\),:;'?!/-]) , re.m | re.s) txt = r.sub( '\\1<em><code>\\2</code></em>\\3' ,txt) # *strongly emphasize* words r = re.compile(r '([\(\s'/]|^)\*(.*?)\*([\s\.\),:;'?!/-]) , re.m | re.s) txt = r.sub( '\\1<strong>\\2</strong>\\3' , txt) # ... [more text massaging] ... return txt # ... [more text transformation functions] .....
要使用最新和最具體的支持模塊,需要一些準備步驟。首先,將主 txt2html 模塊下載到本地系統(tǒng)(這是一次性步驟)。其次,在本地系統(tǒng)上創(chuàng)建類似于以下示例的 python 腳本:
'dyn_txt2html.py' 命令行轉(zhuǎn)換器
?1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 from dmtxt2html import * # import the body of 'txt2html' code from urllib import urlopen import sys # check for updated functions (fail gracefully if not fetchable) try : updates = urlopen( 'http://gnosis.cx/download/t2h_textfuncs.py' ).read() fh = open( 't2h_textfuncs.py' , 'w' ) fh.write(updates) fh.close() except : sys.stderr.write( 'cannot currently download txt2html updates' ) # import the updated functions (if available) try : from t2h_textfuncs import * except : sys.stderr.write( 'cannot import the updated txt2html functions' ) # set options based on runmode (shell vs. cgi) if len(sys.argv) >= 2: cfg_dict = parseargs(sys.argv[1:]) main(cfg_dict) else : printplease specify url (and options) for txt2html conversion
在 dyn_txt2html.py 腳本中,請注意當執(zhí)行 from t2h_textfuncs import * 語句時,所有以前在 dmtxt2html 中定義的函數(shù)(如 typographify() )都將由 t2h_textfuncs 版本的同名函數(shù)替換。當然,如果 t2h_textfuncs 的函數(shù)被注釋掉了,則不會被替換。
有件小問題得注意,不同的系統(tǒng)以不同的方式處理寫入 stderr。在類 unix 系統(tǒng)中,運行腳本時可以重定向 stderr;但是在當前 os/2 外殼和 windows/dos 中,stderr 消息將附加到控制臺輸出。您也許要將以上的錯誤/警告寫到日志文件中,或者只習慣于將 stdout 定向到文件(可能會更有用)。例如:
'dyn_txt2html' 的命令行會話
?12 g:\txt2html> python dyn_txt2html.py test.txt > test.html cannot currently download txt2html updates
錯誤轉(zhuǎn)至控制臺;經(jīng)轉(zhuǎn)換的輸出轉(zhuǎn)至文件。
一件更有趣的事情是 dyn_txt2html.py 為什么不下載整個 dmtxt2html 模塊,而僅下載支持模塊。當然這是有理由的。 t2h_textfuncs 支持模塊遠遠小于主 dmtxt2html 模塊,特別是因為大多數(shù)函數(shù)已經(jīng)過刪節(jié)/被注釋掉。在調(diào)制解調(diào)器連接上,它的速度明顯快很多。但下載大小并不是主要原因。
對于 txt2html,如果用戶自動下載整個最新模塊也沒關(guān)系。但程序邏輯是 分布式 的系統(tǒng)(特別是維護責任也是分布式的)會發(fā)生什么情況呢?您也許會讓 alice、bob 和 charlie 分別負責模塊 funcs_a 、 funcs_b 和 funcs_c 。他們每個人都對他們負責的函數(shù)進行定期(且獨立)更改,并將最新和最好的版本上傳到他們自己的網(wǎng)站(如 )。在這種情況下,讓三個程序員都更改同一個主模塊不太可行。但可以直接擴展類似于 dyn_txt2html.py 的腳本以在啟動時嘗試導入 funcs_a 、 funcs_b 和 funcs_c (如果不能獲取這些資源,則會退到 mainprog 版本)。
長期運行的動態(tài)進程
迄今為止,我們研究的工具已經(jīng)通過在初始化時下載更新資源而獲得了動態(tài)程序邏輯。這對于命令行處理或批處理很有意義,但對于長期運行的應用程序又會怎樣。這種長期運行的應用程序最可能是一些不斷響應客戶機請求的服務器進程。但是在這個案例中,我們將使用為 以前的文章 開發(fā)的 curses_txt2html.py 來說明 python 的 reload() 函數(shù)。程序 curses_txt2html 是 dmtxt2html 本地副本的封裝器。這里并不是第二次提到 curses 編程,談一下 curses_txt2html 提供了一組交互式菜單以配置和運行多個連續(xù)的 txt2html 轉(zhuǎn)換也足夠了。
curses_txt2html 可以一直在后臺運行,當切換到它的會話并運行轉(zhuǎn)換時,我們希望它能夠使用最新的程序邏輯。對于這個特定的簡單示例,關(guān)閉和重新啟動應用程序并不難,并不會帶來特別的損害。但這很容易令人聯(lián)想到其它一直運行著的進程(可能是說明會話中所執(zhí)行操作狀態(tài)的進程)。
在本文中,添加了新的 file/update 子菜單。它被激活時只調(diào)用新的函數(shù) update_txt2html() 。除了與提供發(fā)生的確認相關(guān)的 curses 調(diào)用之外,我們已經(jīng)在本文的其它示例中看到過這些步驟:
'curses_txt2html.py' 動態(tài)更新函數(shù)
?123456789101112131415161718192021222324252627282930313233343536373839 def update_txt2html (): # check for updated functions (fail gracefully if not fetchable) s = curses.newwin(6, 60, 4, 5) s.box() s.addstr(1, 2, * press any key to continue * , curses.a_bold) s.addstr(3,2, ...downloading... ) s.refresh() try : from urllib import urlopen updates = urlopen( 'http://gnosis.cx/download/dmtxt2html.py' ).read() fh = open( 'dmtxt2html.py' , 'w' ) fh.write(updates) fh.close() s.addstr(3,2, module [dmtxt2html] downloaded to current directory ) except : s.addstr(3,2, download of updated [dmtxt2html] module failed! ) reload(dmtxt2html) s.addstr(4, 2, module [dmtxt2html] reloaded from current directory ) s.refresh() c = s.getch() s.erase()
dyn_txthtml.py 和 update_txt2html() 函數(shù)之間有兩個重要差異。其中一個差異是繼續(xù)操作,并導入主 dmtxt2html 模塊而不只導入支持函數(shù)。這主要是簡化了導入。這里的問題是我們使用 import dmtxt2html 來訪問模塊,而不是 from dmtxt2html import * 。從許多方面考慮,這是一個更安全的過程,但結(jié)果是使覆蓋 dmtxt2html 中的函數(shù)變得更困難(不論是無心地還是故意地)。如果我們要從 d2h_textfuncs 附加函數(shù),則必須對導入的支持模塊執(zhí)行 dir() ,并將成員以屬性形式附加到 dmtxt2html 名稱空間。執(zhí)行這種樣式的覆蓋是留給讀者的練習。
update_txt2html() 函數(shù)帶來的最主要差異是 python 的內(nèi)置 reload() 函數(shù)的用法。只執(zhí)行全新的 import dmtxt2html 將 不 會覆蓋以前導入的函數(shù)。請密切注意這一點!許多初學者認為重新導入模塊將更新內(nèi)存中的版本。這是錯的。實際上,更新模塊中函數(shù)的內(nèi)存映像的方法是 reload() 模塊。
以上示例中還執(zhí)行了另一個小技巧。更新 dmtxt2html 模塊的下載位置是本地工作目錄,而這個目錄可能是(也可能不是)原來裝入 dmtxt2html 的目錄。事實上,如果它在 python 庫目錄中,那么您也許不在該目錄中使用(也許對它沒有用戶許可權(quán))。但 reload() 調(diào)用嘗試先從當前目錄裝入,然后再嘗試 python 路徑的其余部分。所以,不論下載是否成功, reload() 應該是一個安全的操作(雖然它可能裝入新的模塊,也可能不裝入)。