某一類 Python應用程序最好使用交互式用戶界面,這樣可以消除圖形環境的系統開銷或復雜性。交互式文本模式程序(在Linux/UNIX 中),例如封裝在 Python 的標准 curses模塊中的 ncurses 庫,正是您所需要的。本文中,DavidMertz 討論了在 Python 中 curses 的用法。他使用從前端到 Txt2Html程序的樣本源代碼闡述了 curses 環境。
curses 庫 ( ncurses ) 提供了控制字符屏幕的獨立於終端的方法。curses 是大多數類似於 UNIX 的系統(包括 Linux)的標准部分,而且它已經移植到 Windows 和其它系統。curses 程序將在純文本系統上、xterm 和其它窗口化控制台會話中運行,這使這些應用程序具有良好的可移植性。
介紹 curses
Python 的標准 curses 提供了“玻璃電傳”(glass teletype)(在 20 世紀 70 年代,原始 curses 庫剛創建時,它叫做 CRT)的公共特性的基本接口。有許多方法可以讓用 Python 編寫的交互式文本模式程序變得更巧妙。這些方法分成兩類。
一方面,有些Python 模塊支持 ncurses (curses 的超集)或 slang (相似卻獨立的控制台庫)的全部功能集合。最值得注意的是,這當中有一個增強庫(由適當的 Python 模塊封裝)可以讓您將顏色添加到界面上。
另一方面,許多構建在curses(或 ncurses / slang )上的高級窗口小部件庫添加了諸如按鈕、菜單、滾動欄和各種公共界面設備之類的特性。如果您看到過用諸如 Borland's TurboWindows(DOS 版)之類的庫開發的應用程序,您就知道在文本模式控制台中,這些特性是多麼吸引人。窗口小部件庫中的功能單單使用 curses 都可以達到,但是還可以利用其它程序員在高級界面上取得的成果。請參閱 參考資料,以尋找所提到的模塊的鏈接。
本文只涉及 curses 自身的特性。由於 curses 模塊是標准發行版的一部分,您不必下載支持庫或其它 Python 模塊就可以找到並使用它(至少在 Linux 或 UNIX 系統中是這樣)。理解 curses 提供的基本支持很有用,即使只是作為理解高級模塊的基礎。即使不使用其它模塊,單獨使用 curses 構建漂亮且實用的 Python 文本模式應用程序也很簡單。預先發行的說明提到 Python 2.0 將包括 curses 的增強版本,但不管怎樣,它應該兼容此處說明的版本。
應用程序
我將討論為 Txt2Html(在 “可愛的 Python:我的第一個基於 Web 的過濾代理” 中介紹的文本到 HTML 轉換程序)編寫的封裝器,作為本文的測試應用程序。Txt2Html 有幾種運行方式。但為了與本文的目的保持一致,我們將研究從命令行運行的 Txt2Html.操作 Txt2Html 的一種方式是向它提供一組命令行變量(它們說明要執行的轉換的各方面),然後將應用程序當作批處理運行。對於偶爾使用的用戶,一個更友好的用戶界面提供了一個交互式選擇屏幕,它可以在執行實際轉換之前,引導用戶遍歷轉換選項(提供選中選項的視覺反饋)。
curses_txt2html 的界面基於常見的頂欄菜單,它帶有下拉和嵌套子菜單。所有菜單相關的功能都在 curses 上“從頭”開始設計。雖然這些菜單缺少更復雜的 curses 封裝器的一些特性,但它們的基本功能是由幾行只使用 curses 的代碼實現的。這個界面還帶有一個簡單的卷動幫助框和幾個用戶輸入字段。以下是顯示常規布局和樣式的應用程序的屏幕快照。
X終端上的應用程序
Linux終端上的應用程序
點擊查看大圖
封裝 curses 應用程序
curses 編程的基本元素是窗口對象。窗口是帶有一個可尋址光標的實際物理屏幕的區域,光標的坐標與窗口相關。可以到處移動窗口,並且可以創建和刪除窗口而不影響其它窗口。在窗口對象中,輸入或輸出操作發生在光標上,這通常由輸入或輸出方法明確設置,但也可以分別修改。
在初始化 curses 之後,可以用各種方式修改或完全禁用面向流的控制台輸入和輸出。這基本上就是使用 curses 的全部重點。可是一旦更改了流式控制台交互,如果程序出錯,將不會以正常方式顯示 Python 追溯事件。Andrew KUChling 使用一個很好的 curses 程序頂級框架解決了這個問題(請參閱 參考資料中他的教程)。
以下模板(基本上與 Kuchling 的相同)保留在正常命令行 Python 的錯誤報告功能:
Python [curses] 程序的頂層設置代碼
import curses, traceback if __name__== '__main__': try : # Initialize curses stdscr=curses.initscr() # Turn off echoing of keys, and enter cbreak mode, # where no buffering is performed on keyboard input curses.noecho() curses.cbreak() # In keypad mode, escape sequences for special keys # (like the cursor keys) will be interpreted and # a special value like curses.KEY_LEFT will be returned stdscr.keypad(1) main(stdscr) # Enter the main loop # Set everything back to normal stdscr.keypad(0) curses.echo() curses.nocbreak() curses.endwin() # Terminate curses except : # In event of error, restore terminal to sane state. stdscr.keypad(0) curses.echo() curses.nocbreak() curses.endwin() traceback.print_exc() # Print the exception
try 代碼塊執行一些初始化,調用 main() 函數來執行實際工作,然後執行最後的清除。如果出錯, except 代碼塊會將控制台恢復成缺省狀態,然後報告遇到的異常。
main() 事件循環
現在,我們研究 main() 函數,看看 curses_txt2html 做些什麼:
curses_txt2html.py main() 函數和事件循環
def main (stdscr): # Frame the interface area at fixed VT100 size global screen screen = stdscr.subwin(23, 79, 0, 0) screen.box() screen.hline(2, 1, curses.ACS_HLINE, 77) screen.refresh() # Define the topbar menus file_menu = ( "File", "file_func()") proxy_menu = ( "Proxy Mode", "proxy_func()") doit_menu = ( "Do It!", "doit_func()") help_menu = ( "Help", "help_func()") exit_menu = ( "Exit", "EXIT") # Add the topbar menus to screen object topbar_menu((file_menu, proxy_menu, doit_menu, help_menu, exit_menu)) # Enter the topbar menu loop while topbar_key_handler(): draw_dict()
根據由空行隔開的三部分,很容易理解 main() 函數。
第一部分執行應用程序外觀的常規設置。為了建立應用程序元素之間的可預期間隔,交互式區域限制在 80 x 25 VT100/PC 屏幕大小(即使實際的終端窗口更大)。程序圍繞這個子窗口繪制一個框,並使用水平線畫出頂欄菜單的視覺偏移量。
第二部分建立應用程序所使用的菜單。函數 topbar_menu() 使用一些技巧將熱鍵綁定到應用程序操作並用期望的視覺屬性來顯示菜單。請獲取源碼檔案(請參閱 參考資料 )以查看所有代碼。 topbar_menu() 應該是非常普通的。(歡迎將它合並到您自己的應用程序中。)非常重要的是一旦綁定了熱鍵,它們就 eval() 與菜單相關的字節組第二個元素中包含的字符串。例如,激活以上設置中的 "File" 菜單將調用 "eval("file_func()")"。所以就要求應用程序定義叫做 file_func() 的函數,要求它返回一個布爾 (Boolean) 值以表示是否達到應用程序終止狀態。
第三部分只有兩行,但這正是整個應用程序實際運行的部分。函數 topbar_key_handler() 就像它的名稱所暗示的:它等待擊鍵,然後處理它們。擊鍵處理程序可以會返回 Boolean false 值。(如果是這樣,則應用程序終止。)該應用程序中,鍵處理程序主要是檢查第二段中綁定的鍵。但即使您的 curses 應用程序綁定鍵的方式與該應用程序不同,您仍要使用類似的事件循環。處理程序的關鍵部分很可能使用以下這行代碼:
c = screen.getch()# read a keypress
對 draw_dict() 的調用只是事件循環中唯一的代碼。此函數繪制了 screen 窗口中幾處位置中的值。但在應用程序中,您可能想要將以下這行代碼:
screen.refresh() # redraw the screen w/ any new output
加到繪制/刷新函數中(或只加到事件循環本身中)。
獲取用戶輸入
curses 應用程序以擊鍵事件的形式獲取所有用戶輸入。我們已經看過了 .getch() 方法,現在讓我們看一下將 .getch() 與其它輸入方法組合在一起的例子 .getstr() 。以下就是我們以前提到的 file_func() 函數的縮寫版本(它由 "File" 菜單激活)。
curses_txt2html.py file_func() 函數
def file_func (): s = curses.newwin(5,10,2,1) s.box() s.addstr(1,2, "I", hotkey_attr) s.addstr(1,3, "nput", menu_attr) s.addstr(2,2, "O", hotkey_attr) s.addstr(2,3, "utput", menu_attr) s.addstr(3,2, "T", hotkey_attr) s.addstr(3,3, "ype", menu_attr) s.addstr(1,2, "", hotkey_attr) s.refresh() c = s.getch() if c in (ord( 'I'), ord( 'i'), curses.KEY_ENTER, 10): curses.echo() s.erase() screen.addstr(5,33, " "*43, curses.A_UNDERLINE) cfg_dict[ 'source'] = screen.getstr(5,33) curses.noecho() else : curses.beep() s.erase() return CONTINUE
此函數組合了幾個 curses 特性。它做的第一件事就是創建另一個窗口對象。由於這個新窗口對象是 "File" 選擇項的實際下拉菜單,所以程序使用 .box() 方法圍著它繪制了一個框架。在窗口 s 中,程序繪制了幾個下拉菜單選項。使用了一種稍微費力的方法突出顯示了每個選項的熱鍵,這樣就與選項描述的其余部分形成了對比。(請查看完整源碼(請參閱 參考資料 )中的 topbar_menu() 以學習一種能稍微自動處理突出顯示的方法。)最後的 .addstr() 調用將光標移到缺省菜單選項。如同主屏幕一樣, s.refresh() 實際上顯示了畫到窗口對象上的元素。
繪制了下拉菜單後,程序使用簡單的 s.getch() 調用來獲取用戶的選擇項。在演示應用程序中,菜單只響應熱鍵,但不響應箭頭鍵或可移動突出顯示欄。可以通過捕捉附加鍵操作並在下拉菜單中設置事件循環來構建這些更復雜的菜單功能。但這個例子已經足夠說明這種概念了。
接著,程序將剛讀取的擊鍵與各種熱鍵值做比較。在本例中,熱鍵的大小寫都可以激活下拉菜單選項,並且可以使用 ENTER 鍵激活缺省選項。(curses 特殊鍵常量看上去並不完全可靠,我發現必須添加實際的 ASCII 值 "10" 來捕捉 ENTER 鍵。)請注意,如果要執行字符值比較,那麼要將字符串封裝到 ord() 內置 Python 函數中。
當選中 "Input" 選項時,程序會使用 .getstr() 方法,該方法提供帶有原始編輯能力的字段輸入(可以使用退格鍵)。由 ENTER 鍵終止輸入,然後方法返回輸入的值。通常會像上例中一樣,將這個值分配給一個變量。
為了在視覺上區別輸入字段,我使用了一點小技巧,預先向將要發生數據輸入的區域添加了下劃線。無論如何,這都是必要的,但它添加了一種視覺效果。由以下這行代碼畫出下劃線:
screen.addstr(5,33, " "*43, curses.A_UNDERLINE)
當然,程序還必須除去下劃線,這項工作在 draw_dict() 刷新函數中由以下這行代碼執行:
screen.addstr(5,33, " "*43, curses.A_NORMAL) 結束語
這裡概述的技術以及在完整應用程序源代碼(請參閱 參考資料 )中使用的那些技術應該可以讓您初步了解 curses 編程。請使用它來編寫您的應用程序。它並不難使用。告訴您一個好消息,除了 Python 以外,有許多語言可以訪問 curses 庫,因此您學到的使用 Python curses 模塊的知識同樣適用於其它語言。
如果經檢驗,基本 curses 模塊不能滿足您的要求,“參考資料”節中提供了許多模塊的鏈接,他們增添了 curses 的功能並提供了非常好的發展方向。
關於作者
David Mertz 相信上帝給了我們鍵盤和 TTY,而其它所有界面設備都是完全是人類的傑作。可以通過 [email protected] 與 David 取得聯系。 http://gnosis.cx/publish/ 上刊登了他寫的文章。非常歡迎對過去的、這一篇或將來的專欄文章提出意見和建議。