讓我們描繪一下本文的情節:假設您要在本地機器上運行一個進程,而部分程序邏輯卻在另一處。讓我們特別假設這個程序邏輯會不時更新, 而您運行進程時,希望使用最新的程序邏輯。有許多方法可以滿足剛提到的要求;本文將向您說明其中幾種方法。
隨著“可愛的 python”專欄不斷進行,已經討論了我的公共域實用程序 txt2html 的正在進行的增強。該實用程序將“智能 ascii”文本文件轉換成 html。以前的文章討論了實用程序的 web 代理版本和實用程序的 curses 界面。同樣,我偶爾注意到可以用更有效的方法轉換某些 ascii 標記,或者解決了一個在處理某個特殊標記結構中的錯誤。
事實上,本專欄的文章都是用 ascii 編寫的,然后在編輯過程中轉換成您可以閱讀的 html 格式。在發表文章草稿之前,我運行了類似以下處理的程序:
文章的命令行 html 化
?1 txt2html charming_python_7.txt > charming_python_7.html
如果愿意,我可以指定一些標志來修改操作;但不管怎樣,事實上轉換器的最新版本在我的本地驅動器和路徑中。如果在另一臺機器上工作,或者對于要使用該實用程序的讀者,則過程比較麻煩:請訪問我的網站,注意比較版本號和文件日期(有時更改太小,我不會更改版本號),下載當前版本、將當前版本復制到正確目錄,然后運行命令行轉換器。(請參閱本文后面的 參考資料。)
以上的過程包括幾個需要手工操作且比較費時的步驟。應該更簡單,而且可以做到這點。
命令行 web 訪問
大多數人認為 web 是在 gui 環境中交互式瀏覽頁面的一種方法。那樣做當然很好,但命令行中也有許多功能。帶文本模式 web 瀏覽器 lynx 的系統完全可以將整個 web 看作是命令行工具使用的另一個文件集。例如,我發現有些命令很有用:
使用 lynx 進行命令行 web 瀏覽
?123 lynx -dump . lynx -dump . > ibm_developer.txt lynx -dump | wc | sed s/( *[0-9]* *\)\([0-9]*\)\(.*\)/\2/g
第一行說:“將 david mertz 的主頁(以 ascii 文本)顯示到控制臺。”第二行說:“將 ibm 的當前 developerworks 主頁的 ascii 版本保存到文件?!钡谌惺纠f:“顯示 david 主頁的字數?!保ú槐負募毠潱伙@示與管道結合的命令行工具。)
關于 lynx,有一點要注意它(使用 -dump 選項時)執行幾乎與 txt2html 完全相反的操作:前一種工具將 html 轉換成文本;而后一種工具則轉換成其它格式。但沒有理由不使用與 lynx 一樣流行的 txt2html。可以使用一個很短的 python 腳本完成這個操作:
'fetch_txt2html.py' 命令行轉換器
?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
要運行這個腳本,只要執行如下操作:
?1 python fetch_txt2html.py
這并沒有向您提供本地 txt2html 處理的全部開關,但如有必要,添加它們也很容易。可以像使用任何命令行工具一樣來輸送和重定向輸出。但是,在上述版本中,只能處理 url 可以到達的數據文件,而不能處理本地文件。
實際上, fetch_txt2html.py 可以完成 lynx 不能完成的任務(txt2html 本身也不能):它不僅從 url 取得數據源,而且還遠程獲取 程序邏輯 。如果使用 fetch_txt2html.py ,就 不必在本地機器上安裝 txt2html;將(使用最新版本)遠程調用處理,并且將把結果發送回來,就像運行的是本地進程。很棒吧?txt2html 的本地版本可以訪問遠程 url,就像訪問本地文件一樣,但它還不能保證它自身是最新的……!
動態初始化
使用 fetch_txt2html.py 確保了在轉換中始終使用最新的程序邏輯。但是,這個方法可以完成的另一件事情是將處理器(和內存)的需求轉移給 gnosis.cx web 服務器。此特殊進程的負載并不是特別高,但人們卻很可能認為在客戶機上處理的其它類型的進程會更有效且令人滿意。
組織 txt2html 的方式 -- 也就是組織大多數程序的方式 -- 是用一些由各種實用函數提供的核心流量控制函數。尤其是這些實用函數是一些經常更新的函數;核心函數( main() 和一些其它函數)只有在做重大改寫時才會變動??偠灾诿總€程序運行時有效更新的就是實用函數。其實,大部分情況下,主 txt2html 模塊 dmtxt2html 中的大多數函數就夠了。
'd2h_textfuncs.py' 動態 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 模塊下載到本地系統(這是一次性步驟)。其次,在本地系統上創建類似于以下示例的 python 腳本:
'dyn_txt2html.py' 命令行轉換器
?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 腳本中,請注意當執行 from t2h_textfuncs import * 語句時,所有以前在 dmtxt2html 中定義的函數(如 typographify() )都將由 t2h_textfuncs 版本的同名函數替換。當然,如果 t2h_textfuncs 的函數被注釋掉了,則不會被替換。
有件小問題得注意,不同的系統以不同的方式處理寫入 stderr。在類 unix 系統中,運行腳本時可以重定向 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
錯誤轉至控制臺;經轉換的輸出轉至文件。
一件更有趣的事情是 dyn_txt2html.py 為什么不下載整個 dmtxt2html 模塊,而僅下載支持模塊。當然這是有理由的。 t2h_textfuncs 支持模塊遠遠小于主 dmtxt2html 模塊,特別是因為大多數函數已經過刪節/被注釋掉。在調制解調器連接上,它的速度明顯快很多。但下載大小并不是主要原因。
對于 txt2html,如果用戶自動下載整個最新模塊也沒關系。但程序邏輯是 分布式 的系統(特別是維護責任也是分布式的)會發生什么情況呢?您也許會讓 alice、bob 和 charlie 分別負責模塊 funcs_a 、 funcs_b 和 funcs_c 。他們每個人都對他們負責的函數進行定期(且獨立)更改,并將最新和最好的版本上傳到他們自己的網站(如 )。在這種情況下,讓三個程序員都更改同一個主模塊不太可行。但可以直接擴展類似于 dyn_txt2html.py 的腳本以在啟動時嘗試導入 funcs_a 、 funcs_b 和 funcs_c (如果不能獲取這些資源,則會退到 mainprog 版本)。
長期運行的動態進程
迄今為止,我們研究的工具已經通過在初始化時下載更新資源而獲得了動態程序邏輯。這對于命令行處理或批處理很有意義,但對于長期運行的應用程序又會怎樣。這種長期運行的應用程序最可能是一些不斷響應客戶機請求的服務器進程。但是在這個案例中,我們將使用為 以前的文章 開發的 curses_txt2html.py 來說明 python 的 reload() 函數。程序 curses_txt2html 是 dmtxt2html 本地副本的封裝器。這里并不是第二次提到 curses 編程,談一下 curses_txt2html 提供了一組交互式菜單以配置和運行多個連續的 txt2html 轉換也足夠了。
curses_txt2html 可以一直在后臺運行,當切換到它的會話并運行轉換時,我們希望它能夠使用最新的程序邏輯。對于這個特定的簡單示例,關閉和重新啟動應用程序并不難,并不會帶來特別的損害。但這很容易令人聯想到其它一直運行著的進程(可能是說明會話中所執行操作狀態的進程)。
在本文中,添加了新的 file/update 子菜單。它被激活時只調用新的函數 update_txt2html() 。除了與提供發生的確認相關的 curses 調用之外,我們已經在本文的其它示例中看到過這些步驟:
'curses_txt2html.py' 動態更新函數
?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() 函數之間有兩個重要差異。其中一個差異是繼續操作,并導入主 dmtxt2html 模塊而不只導入支持函數。這主要是簡化了導入。這里的問題是我們使用 import dmtxt2html 來訪問模塊,而不是 from dmtxt2html import * 。從許多方面考慮,這是一個更安全的過程,但結果是使覆蓋 dmtxt2html 中的函數變得更困難(不論是無心地還是故意地)。如果我們要從 d2h_textfuncs 附加函數,則必須對導入的支持模塊執行 dir() ,并將成員以屬性形式附加到 dmtxt2html 名稱空間。執行這種樣式的覆蓋是留給讀者的練習。
update_txt2html() 函數帶來的最主要差異是 python 的內置 reload() 函數的用法。只執行全新的 import dmtxt2html 將 不 會覆蓋以前導入的函數。請密切注意這一點!許多初學者認為重新導入模塊將更新內存中的版本。這是錯的。實際上,更新模塊中函數的內存映像的方法是 reload() 模塊。
以上示例中還執行了另一個小技巧。更新 dmtxt2html 模塊的下載位置是本地工作目錄,而這個目錄可能是(也可能不是)原來裝入 dmtxt2html 的目錄。事實上,如果它在 python 庫目錄中,那么您也許不在該目錄中使用(也許對它沒有用戶許可權)。但 reload() 調用嘗試先從當前目錄裝入,然后再嘗試 python 路徑的其余部分。所以,不論下載是否成功, reload() 應該是一個安全的操作(雖然它可能裝入新的模塊,也可能不裝入)。