On this page
用 Pythoncurses 编程
Author
- 上午。埃里克·雷蒙德·库奇林
Release
- 2.03
Abstract
本文档介绍了如何使用curses扩展模块控制显示,并使用 Python 2.x 编写文本模式程序。
什么是 curses?
curses 库为基于文本的终端提供了独立于终端的屏幕绘画和键盘处理Function;此类终端包括 VT100,Linux 控制台以及 X11 程序(如 xterm 和 rxvt)提供的模拟终端。显示终端支持各种控制代码以执行常见的操作,例如移动光标,滚动屏幕和擦除区域。不同的终端使用相差很大的代码,并且常常有自己的小怪癖。
在 X 显示器的世界里,人们可能会问“为什么要打扰”?字符单元显示终端确实是一种过时的技术,但是在某些领域,能够用它们来做花哨的事情仍然很有价值。一个是在不带 X 服务器的小型或嵌入式 Unix 上。另一个是针对 OS 安装程序和内核配置程序之类的工具,这些工具可能必须在 X 可用之前运行。
curses 库隐藏了不同终端的所有详细信息,并为程序员提供了一个包含多个不重叠窗口的显示抽象。窗口的内容可以pass各种方式更改-添加文本,擦除文本,更改其外观-并且 curses 库将自动确定需要向终端发送哪些控制代码以产生正确的输出。
curses 库最初是为 BSD Unix 编写的。 AT&T 的 Unix 的后来的 System V 版本增加了许多增强Function和新Function。 BSD curses 不再维护,已被 ncurses 取代,ncurses 是 AT&T 接口的开源实现。如果您使用的是 Linux 或 FreeBSD 等开源 Unix,则您的系统几乎肯定会使用 ncurses。由于大多数当前的商业 Unix 版本都基于 System V 代码,因此这里描述的所有Function可能都可用。但是,某些专有 Unix 所带来的较早版本的 curses 可能不支持所有Function。
没有人为 curses 模块提供 Windows 端口。在 Windows 平台上,请try使用 Fredrik Lundh 编写的 Console 模块。控制台模块提供光标可寻址的文本输出,以及对鼠标和键盘 Importing 的全面支持,并且可从http://effbot.org/zone/console-index.htm获得。
Python curses 模块
您的 Python 模块是对 curses 提供的 C 函数的相当简单的包装;如果您已经熟悉 C 语言中的 curses 编程,那么将这些知识转移到 Python 上真的很容易。最大的区别在于,Python 界面pass将不同的 C 函数(例如addstr()
,mvaddstr()
,mvwaddstr()
)合并为单个addstr()
方法,使事情变得更简单。稍后,您将看到更详细的内容。
本 HOWTO 只是使用 curses 和 Python 编写文本模式程序的简介。它并没有试图成为 curses API 的完整指南。为此,请参见有关 ncurses 的 Python 库指南部分,以及有关 ncurses 的 C 手册页。但是,它将为您提供基本的想法。
开始和结束 curses 申请
在做任何事情之前,必须先初始化 curses。这是pass调用initscr()
函数来完成的,该函数将确定终端类型,将所需的所有设置代码发送到终端,并创建各种内部数据结构。如果成功,则initscr()
返回代表整个屏幕的窗口对象;通常在对应的 C 变量的名称之后称为stdscr
。
import curses
stdscr = curses.initscr()
通常,curses 应用程序会关闭自动向屏幕显示按键的Function,以便能够读取按键并仅在某些情况下显示它们。这需要调用noecho()
函数。
curses.noecho()
应用程序通常还需要立即对键做出反应,而无需按下 Enter 键。与通常的缓冲 Importing 模式相反,这称为 cbreak 模式。
curses.cbreak()
终端通常以多字节转义序列的形式返回特殊键,例如光标键或导航键,例如 Page Up 和 Home。尽管您可以编写应用程序来期望这样的序列并进行相应的处理,但是 curses 可以为您完成,返回一个特殊值,例如curses.KEY_LEFT
。要获得 curses 来完成这项工作,您必须启用键盘模式。
stdscr.keypad(1)
终止 curses 应用程序比启动应用程序容易得多。你需要打电话
curses.nocbreak(); stdscr.keypad(0); curses.echo()
反转对 curses 友好的终端设置。然后调用endwin()
函数将终端恢复到其原始操作模式。
curses.endwin()
调试 curses 应用程序时,常见的问题是在应用程序死机时弄乱了终端,而没有将终端恢复到以前的状态。在 Python 中,这通常发生在您的代码有错误并引发未捕获的异常时。例如,键入键时,键不再在屏幕上回显,这使使用 Shell 变得困难。
在 Python 中,您可以pass导入curses.wrapper()函数来避免这些复杂性并使调试更加容易。它需要调用并进行上述初始化,如果存在颜色支持,则还要初始化颜色。然后,它将运行您提供的可调用对象,并finally进行适当的初始化。可调用对象在 try-catch 子句中调用,该子句捕获异常,执行 curses 反初始化,然后向上传递异常。因此,您的终端不会因为异常而处于有趣的状态。
Windows 和 Pads
Windows 是 curses 的基本抽象。窗口对象代表屏幕的矩形区域,并支持各种方法来显示文本,擦除文本,允许用户 Importing 字符串等。
initscr()
函数返回的stdscr
对象是一个覆盖整个屏幕的窗口对象。许多程序可能只需要一个窗口,但是您可能希望将屏幕分成较小的窗口,以便分别重绘或清除它们。 newwin()
函数创建给定大小的新窗口,并返回新的窗口对象。
begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
关于 curses 中使用的坐标系的一句话:坐标始终以* y,x 的 Sequences 传递,并且窗口的左上角为坐标(0,0)。这 break 了处理坐标的通用约定,通常以 x *坐标为第一位。不幸的是,这与大多数其他计算机应用程序有所不同,但是自从第一次编写以来,这一直是 curses 的一部分,现在改变现状为时已晚。
当您调用显示或删除文本的方法时,效果不会立即显示在显示屏上。这是因为 curses 最初是在考虑 300 波特的慢速终端连接的情况下编写的;使用这些终端,尽量减少重新绘制屏幕所需的时间非常重要。这使 curses 可以累积对屏幕的更改,并以最有效的方式显示它们。例如,如果您的程序在窗口中显示了一些字符,然后清除了该窗口,则无需发送原始字符,因为它们将永远不可见。
因此,curses 要求您使用窗口对象的refresh()
方法明确地告诉它重绘窗口。实际上,这并不会使编程复杂得多。大多数程序都会进行一系列活动,然后暂停以 await 用户方面的按键或其他操作。您所要做的就是确保在暂停 await 用户 Importing 之前已重绘了屏幕,只需简单地调用其他相关窗口的stdscr.refresh()
或refresh()
方法即可。
垫子是窗户的特例;它可以大于实际显示屏,并且一次只显示其中一部分。创建垫板仅需要垫板的高度和宽度,而刷新垫板则需要提供将在其中显示垫板子部分的屏幕区域的坐标。
pad = curses.newpad(100, 100)
# These loops fill the pad with letters; this is
# explained in the next section
for y in range(0, 100):
for x in range(0, 100):
try:
pad.addch(y,x, ord('a') + (x*x+y*y) % 26)
except curses.error:
pass
# Displays a section of the pad in the middle of the screen
pad.refresh(0,0, 5,5, 20,75)
refresh()
呼叫在屏幕上以矩形显示从坐标(5,5)延伸到坐标(20,75)的垫的一部分;显示部分的左上角是打击板上的坐标(0,0)。除此以外,垫完全类似于普通的窗户并支持相同的方法。
如果屏幕上有多个窗口和垫,则有更有效的处理方法,可以防止刷新时屏幕闪烁。使用每个窗口的noutrefresh()
方法来更新表示屏幕所需状态的数据结构;然后使用doupdate()
一次性更改物理屏幕以使其与所需状态匹配。普通的refresh()
方法将doupdate()
作为其最后动作。
Displaying Text
从 C 程序员的角度来看,curses 有时看起来像是一堆曲折的函数迷宫,它们之间有着微妙的不同。例如,addstr()
在stdscr
窗口中的当前光标位置显示一个字符串,而mvaddstr()
在显示该字符串之前先移至给定的 y,x 坐标。 waddstr()
就像addstr()
一样,但是允许指定要使用的窗口,而不是默认情况下使用stdscr
。 mvwaddstr()
也类似。
幸运的是,Python 接口隐藏了所有这些细节。 stdscr
是任何其他窗口对象,而addstr()
等方法则接受多个参数形式。通常有四种不同的形式。
Form | Description |
---|---|
* str 或 ch * | 在当前位置显示字符串* str 或字符 ch * |
* str 或 ch , attr * | 在当前位置使用属性* attr 显示字符串 str 或字符 ch * |
* y , x , str 或 ch * | 移动到窗口内的* y,x 位置,并显示 str 或 ch * |
* y , x , str 或 ch , attr * | 使用属性* attr 移至窗口内的 y,x 位置并显示 str 或 ch * |
属性允许以突出显示的形式显示文本,例如以粗体,下划线,反向代码或彩色显示。下一部分将对它们进行详细说明。
addstr()
函数采用 Python 字符串作为要显示的值,而addch()
函数采用字符,该字符可以是长度为 1 的 Python 字符串或整数。如果是字符串,则只能显示 0 到 255 之间的字符。SVr4 curses 为扩展字符提供常量。这些常量是大于 255 的整数。例如,ACS_PLMINUS
是/符号,而ACS_ULCORNER
是框的左上角(方便绘制边框)。
Windows 会记住上次操作后光标留在的位置,因此,如果Ellipsis* y,x *坐标,则在上次操作break的地方都会显示字符串或字符。您也可以使用move(y,x)
方法移动光标。由于某些终端始终显示闪烁的光标,因此您可能要确保将光标定位在不会分散注意力的某个位置。使光标在某些明显随机的位置闪烁可能会造成混淆。
如果您的应用程序根本不需要闪烁的光标,则可以调用curs_set(0)
使其不可见。同样,为了与较早版本的 curses 兼容,提供了leaveok(bool)
函数。当* bool *为 true 时,curses 库将try抑制闪烁的光标,而您无需担心将其放置在奇数个位置。
属性和颜色
字符可以以不同的方式显示。基于文本的应用程序中的状态行通常以反向视频显示;文本查看器可能需要突出显示某些单词。 curses pass允许您为屏幕上的每个单元格指定一个属性来支持这一点。
属性是一个整数,每个位代表一个不同的属性。您可以try显示设置了多个属性位的文本,但是 curses 不能保证所有可能的组合都可用,或者它们在视觉上完全不同。这取决于所用终端的能力,因此坚持此处列出的最常用的属性是最安全的。
Attribute | Description |
---|---|
A_BLINK |
Blinking text |
A_BOLD |
超亮或粗体 Literals |
A_DIM |
半亮文本 |
A_REVERSE |
Reverse-video text |
A_STANDOUT |
最好的突出显示模式 |
A_UNDERLINE |
Underlined text |
因此,要在屏幕顶部显示反向视频状态行,可以编写以下代码:
stdscr.addstr(0, 0, "Current mode: Typing mode",
curses.A_REVERSE)
stdscr.refresh()
curses 库还支持提供这些颜色的终端上的颜色。最常见的此类终端可能是 Linux 控制台,其后是 color xterms。
若要使用颜色,必须在调用initscr()
之后立即调用start_color()
函数,以初始化默认颜色集(curses.wrapper.wrapper()
函数自动执行此操作)。完成后,如果使用中的终端可以实际显示颜色,则has_colors()
函数将返回 TRUE。 (注意:Curses 使用美国拼写“颜色”,而不是加拿大/英国拼写“颜色”.如果您习惯了英国拼写,则由于这些Function,您必须辞职以使其拼写错误.)
curses 库维护有限数量的颜色对,其中包含前景色(或文本)和背景色。您可以使用color_pair()
函数获得与颜色对相对应的属性值。这可以与其他属性(例如A_REVERSE
)进行按位或运算,但同样,不能保证此类组合在所有终端上都有效。
一个示例,它使用颜色对 1 显示一行文本:
stdscr.addstr("Pretty text", curses.color_pair(1))
stdscr.refresh()
如前所述,颜色对由前景色和背景色组成。 start_color()
激活颜色模式时会初始化 8 种基本颜色。它们是:0:黑色,1:红色,2:绿色,3:黄色,4:蓝色,5:洋红色,6:青色和 7:白色。 curses 模块为以下每种颜色定义命名常量:curses.COLOR_BLACK
,curses.COLOR_RED
等。
init_pair(n, f, b)
函数将颜色对* n *的定义更改为前景色 f 和背景色 b。颜色对 0 硬连线为黑底白字,无法更改。
让我们将所有这些放在一起。要将颜色 1 更改为白色背景上的红色文本,请调用:
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
更改颜色对时,已经使用该颜色对显示的任何文本都将更改为新颜色。您还可以使用以下颜色显示新文本:
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))
非常漂亮的终端可以将实际颜色的定义更改为给定的 RGB 值。这使您可以将通常为红色的颜色 1 更改为紫色或蓝色或您喜欢的任何其他颜色。不幸的是,Linux 控制台不支持此Function,因此我无法try它,并且无法提供任何示例。您可以pass调用can_change_color()
来检查终端是否可以执行此操作,如果存在该Function,则返回 TRUE。如果您有幸拥有如此出色的终端机,请查阅系统的手册页以获取更多信息。
User Input
curses 库本身仅提供非常简单的 Importing 机制。 Python 的支持增加了一个文本 Importing 小部件,弥补了其中的不足。
获取窗口 Importing 的最常见方法是使用其getch()
方法。 getch()
暂停并 await 用户敲击按键,如果echo()
较早被调用,则显示该按键。您可以有选择地指定在暂停之前光标应移动到的坐标。
可以使用方法nodelay()
更改此行为。在nodelay(1)
之后,窗口的getch()
变为非阻塞状态,并且在没有任何 Importing 准备就绪时返回curses.ERR
(值-1)。还有一个halfdelay()
函数,可用于(实际上)在每个getch()
上设置一个计时器;如果在指定的延迟(以十分之一秒为单位)内没有 Importing 变为可用,则 curses 会引发异常。
getch()
方法返回一个整数;如果介于 0 和 255 之间,则表示所按下键的 ASCII 码。大于 255 的值是特殊键,例如 Page Up,Home 或光标键。您可以将返回的值与curses.KEY_PPAGE
,curses.KEY_HOME
或curses.KEY_LEFT
等常量进行比较。通常,程序的主循环如下所示:
while 1:
c = stdscr.getch()
if c == ord('p'):
PrintDocument()
elif c == ord('q'):
break # Exit the while()
elif c == curses.KEY_HOME:
x = y = 0
curses.ascii模块提供了采用整数或 1 个字符的字符串参数的 ASCII 类成员资格函数;这些对于为命令解释器编写更具可读性的测试可能很有用。它还提供采用整数或 1 个字符的字符串参数并返回相同类型的转换函数。例如,curses.ascii.ctrl()返回与其参数相对应的控制字符。
还有一种方法可以检索整个字符串getstr()
。它很少使用,因为它的Function非常有限。唯一可用的编辑键是 Backspace 键和 Enter 键,用于终止字符串。可以选择将其限制为固定数量的字符。
curses.echo() # Enable echoing of characters
# Get a 15-character string, with the cursor on the top line
s = stdscr.getstr(0,0, 15)
Python curses.textpad模块提供了更好的Function。有了它,您可以将窗口变成支持类似 Emacs 的一组键绑定的文本框。 Textbox
类的各种方法都支持使用 Importing 验证进行编辑,并在有或没有尾随空格的情况下收集编辑结果。有关详细信息,请参见curses.textpad上的库文档。
有关更多信息
本 HOWTO 未涵盖一些高级主题,例如屏幕抓取或从 xterm 实例捕获鼠标事件。但是 curses 模块的 Python 库页面现在已经很完整了。您应该接下来浏览它。
如果您不确定任何 ncurses 入口点的详细行为,请查阅您的 curses 实现的手册页,无论是 ncurses 还是专有的 Unix 供应商。手册页将记录所有怪癖,并提供所有可用Function,属性和ACS_*
字符的完整列表。
由于 curses API 太大,因此 Python 界面不支持某些Function,这不是因为它们难以实现,而是因为现在还没有人需要它们。随时添加它们,然后提交补丁。另外,我们还不支持与 ncurses 关联的菜单库。随时添加。
如果您编写了一个有趣的 Servlets,请随时将其贡献为另一个演示。我们总是可以使用更多它们!
ncurses 常见问题解答:http://invisible-island.net/ncurses/ncurses.faq.html