可爱的 Python:Curses 编程

某一类 Python 应用程序最好使用交互式用户界面,这样可以消除图形环境的系统开销或复杂性。交互式文本模式程序(在 Linux/UNIX 中),例如封装在 Python 的标准 curses 模块中的 ncurses 库,正是您所需要的。本文中,David Mertz 讨论了在 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终端上的应用程序
Screenshot of curses_txt2html.py

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() 函数和事件循环

defmain(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() 函数

deffile_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 cin (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 的功能并提供了非常好的发展方向。

参考资料

  • Andrew Kuchling 编写了curses 编程的介绍性教程,名称是Curses Programming With Python。本文中的一部分就受到了 Kuchling 的例子启发,尽管它涵盖了 curses 编程的不同元素(主要在更高层次上)。
  • 请访问最好的常规初学者园地,以获取关于 Python 中基于文本的用户界面工具的信息。
  • Pythonncurses 是一个增强模块,与 Python 1.5.2 curses 相比,它支持更多ncurses 功能。有一些初步计划设想让ncurses 替换掉 Python 2.0.ncurses 中的 curses。
  • Tinter 是构建在 curses 上的高级窗口小部件的一个模块。Tinter 支持按钮、文本框、对话框和进展栏。
  • 一种没有充分引起公众注意的(并且难以捕捉到的) ncurses 和其它各种封装器的备用产品结合了slangnewt 以及 python 封装器模块snackslang 的功能与 curses 一样,而且newt 的功能与Tinter 一样。
  • 请查看snack 的一些例子。
  • pcrt 是用于直接 ANSI 转义码屏幕访问的模块。它使用特殊颜色和属性将字符写到屏幕的特定位置上。它是一个低级接口(甚至比 curses 更低级),它只能在支持 ANSI 转义码(占了转义码的大多数)的控制台上工作,但它却是为文本模式应用程序添加闪烁的好方法。
  • dialog 是一个类似于 Linuxdialog 实用程序的 Python 封装器。实用程序(以及它的 Python 封装器)可以创建“是/否”、菜单、输入、消息文本、信息复选列表和单选列表对话框。如果没有平台问题,您可以使用这个实用程序和模块迅速完成大量工作(当然,目标 Linux 发行版必须有dialog)。
  • 下载本文中使用和提到的文件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值