curses用python编程

作者

A.M.Kuchling,Eric S.Raymond

释放

2.04

摘要

本文档介绍如何使用 curses 显示扩展模块到控制文本模式。

curses是什么?

Curses库为基于文本的终端提供了一个独立于终端的屏幕绘制和键盘处理设备;这些终端包括VT100S、Linux控制台和各种程序提供的模拟终端。显示终端支持各种控制代码来执行常见操作,如移动光标、滚动屏幕和擦除区域。不同的终端使用广泛不同的代码,并且常常有自己的小怪癖。

在一个图形显示的世界里,人们可能会问“为什么要麻烦”?的确,字符单元显示终端是一种过时的技术,但在某些细分市场中,能够用它们做一些花哨的事情仍然很有价值。一个利基是不运行X服务器的小型封装或嵌入式Unix。另一个是一些工具,如OS安装程序和内核配置程序,它们可能必须在任何图形支持可用之前运行。

Curses库提供了相当基本的功能,为程序员提供了包含多个非重叠文本窗口的显示的抽象。一个窗口的内容可以通过各种方式改变——添加文本、删除文本、改变其外观——Curses库将找出需要发送到终端以产生正确输出的控制代码。Curses不提供许多用户界面概念,如按钮、复选框或对话框;如果需要这些功能,请考虑用户界面库,如 Urwid .

Curses库最初是为BSD Unix编写的;AT&T的较新System V版Unix增加了许多增强功能和新功能。BSDcurses不再被维护,已被ncurses替换,ncurses是AT&T接口的开源实现。如果您使用的是开源的Unix,如Linux或FreeBSD,那么您的系统几乎肯定会使用ncurse。由于大多数当前的商业Unix版本都基于SystemV代码,因此这里描述的所有功能都可能可用。不过,一些专有unix所承载的旧版curses可能并不支持所有内容。

python的Windows版本不包括 curses 模块。移植的版本称为 UniCurses 是可用的。你也可以试试 the Console module 由Fredrik Lundh编写,它不使用与Curses相同的API,但提供了光标可寻址的文本输出和对鼠标和键盘输入的完全支持。

Python curses模块

python模块是curses提供的C函数的一个相当简单的封装器;如果您已经熟悉了C语言中的curses编程,那么将这些知识转移到python上就非常容易了。最大的区别在于,通过合并不同的C函数(如 addstr()mvaddstr()mvwaddstr() 成单一 addstr() 方法。稍后您将看到更详细的介绍。

本文介绍了如何用curses和python编写文本模式程序。它并不试图成为Curses API的完整指南;为此,请参阅Python库指南中有关ncurses的部分,以及ncurses的C手册页。但是,它会给你一些基本的想法。

开始和结束Curses应用程序

在执行任何操作之前,必须初始化curses。这是通过调用 initscr() 函数,它将确定终端类型,向终端发送任何所需的设置代码,并创建各种内部数据结构。如果成功, initscr() 返回表示整个屏幕的窗口对象;通常调用 stdscr 在对应C变量的名称之后。::

import curses
stdscr = curses.initscr()

通常情况下,Curses应用程序会关闭屏幕上按键的自动回送功能,以便能够读取按键并仅在特定情况下显示它们。这需要调用 noecho() 功能。::

curses.noecho()

应用程序通常还需要立即对键作出反应,而不需要按下Enter键;这称为cbreak模式,与通常的缓冲输入模式不同。::

curses.cbreak()

终端通常返回特殊键,如光标键或导航键(如page up和home),作为多字节转义序列。虽然您可以编写应用程序来期望这样的序列并进行相应的处理,但Curses可以为您这样做,返回一个特殊值,例如 curses.KEY_LEFT . 要让curses来做这项工作,你必须启用键盘模式。::

stdscr.keypad(True)

终止Curses应用程序比启动应用程序容易得多。您需要调用:

curses.nocbreak()
stdscr.keypad(False)
curses.echo()

逆转curses友好终端设置。然后调用 endwin() 功能将终端恢复到其原始操作模式。::

curses.endwin()

调试Curses应用程序时的一个常见问题是,当应用程序死亡时,如果不将终端恢复到以前的状态,则会使终端出现混乱。在Python中,这通常发生在代码有问题并且引发未捕获的异常时。例如,当您键入键时,它们将不再回显到屏幕上,这使得使用shell变得困难。

在python中,通过导入 curses.wrapper() 功能和使用方法如下:

from curses import wrapper

def main(stdscr):
    # Clear screen
    stdscr.clear()

    # This raises ZeroDivisionError when i == 10.
    for i in range(0, 11):
        v = i-10
        stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))

    stdscr.refresh()
    stdscr.getkey()

wrapper(main)

这个 wrapper() 函数接受一个可调用对象并执行上面描述的初始化,如果存在颜色支持,则还初始化颜色。 wrapper() 然后运行您提供的Callable。一旦可调用项返回, wrapper() 将恢复终端的原始状态。可调用的在 try …… except 它捕获异常,恢复终端的状态,然后重新引发异常。因此,您的终端不会在异常时处于有趣的状态,您将能够读取异常的消息并进行回溯。

Windows和Pads

窗口是curses中的基本抽象。窗口对象表示屏幕的矩形区域,并支持显示文本、擦除文本、允许用户输入字符串等方法。

这个 stdscr 返回的对象 initscr() 函数是一个覆盖整个屏幕的窗口对象。许多程序可能只需要这一个窗口,但您可能希望将屏幕划分为较小的窗口,以便分别重新绘制或清除它们。这个 newwin() 函数创建一个给定大小的新窗口,并返回新窗口对象。::

begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)

注意,在curses中使用的坐标系是不寻常的。坐标总是按顺序传递 y,x ,窗口的左上角是坐标(0,0)。这打破了处理坐标的常规惯例, x 坐标第一。这与大多数其他的计算机应用程序是一个不幸的不同,但自从它第一次被写出来后,它就成了curses的一部分,现在改变它已经太晚了。

您的应用程序可以使用 curses.LINEScurses.COLS 变量以获取 yx 尺寸。法律坐标将从 (0,0)(curses.LINES - 1, curses.COLS - 1) .

当您调用一个方法来显示或删除文本时,效果不会立即显示在显示器上。相反,你必须调用给 refresh() 窗口对象更新屏幕的方法。

这是因为curses最初是在考虑到300波特缓慢的终端连接的情况下编写的;对于这些终端,最小化重新绘制屏幕所需的时间非常重要。相反,curses会累积对屏幕的更改,并在调用时以最有效的方式显示这些更改。 refresh() . 例如,如果程序在窗口中显示一些文本,然后清除窗口,则不需要发送原始文本,因为它们永远不可见。

在实践中,显式地告诉Curses重绘一个窗口并不会使编程复杂化。大多数程序进入一系列活动,然后暂停等待按键或用户的其他操作。您所要做的就是确保在暂停等待用户输入之前,首先调用 stdscr.refresh()refresh() 其他相关窗口的方法。

PAD是窗口的一种特殊情况;它可以大于实际的显示屏幕,并且一次只能显示一部分PAD。创建焊盘需要焊盘的高度和宽度,而刷新焊盘则需要给出显示焊盘一部分的屏幕区域的坐标。::

pad = curses.newpad(100, 100)
# These loops fill the pad with letters; addch() is
# explained in the next section
for y in range(0, 99):
    for x in range(0, 99):
        pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Displays a section of the pad in the middle of the screen.
# (0,0) : coordinate of upper-left corner of pad area to display.
# (5,5) : coordinate of upper-left corner of window area to be filled
#         with pad content.
# (20, 75) : coordinate of lower-right corner of window area to be
#          : filled with pad content.
pad.refresh( 0,0, 5,5, 20,75)

这个 refresh() 调用在屏幕上显示从坐标(5,5)延伸到坐标(20,75)的矩形中的一个焊盘部分;显示部分的左上角是焊盘上的坐标(0,0)。除此之外,垫子和普通Windows完全一样,支持相同的方法。

如果屏幕上有多个窗口和护垫,则有一种更有效的方法来更新屏幕,并在屏幕的每个部分更新时防止恼人的屏幕闪烁。 refresh() 实际上做了两件事:

  1. 调用 noutrefresh() 方法更新表示屏幕所需状态的基础数据结构。

  2. 调用函数 doupdate() 函数更改物理屏幕以匹配数据结构中记录的所需状态。

相反,你可以调用 noutrefresh() 在多个窗口上更新数据结构,然后调用 doupdate() 更新屏幕。

显示文本

从C程序员的角度来看,curses有时看起来像是一个曲折的函数迷宫,所有这些都有细微的不同。例如, addstr() 在当前光标位置显示字符串 stdscr 窗口,而 mvaddstr() 在显示字符串之前,先移动到给定的Y、X坐标。 waddstr() 就像是 addstr() ,但允许指定要使用的窗口,而不是使用 stdscr 默认情况下。 mvwaddstr() 允许同时指定窗口和坐标。

幸运的是,python接口隐藏了所有这些细节。 stdscr 窗口对象是否与其他任何对象一样,以及 addstr() 接受多个参数形式。通常有四种不同的形式。

形式

描述

strch

显示字符串 str 或字符 ch 在当前位置

strchattr

显示字符串 str 或字符 ch ,使用属性 attr 在当前位置

yxstrch

移动到位置 y,x 在窗口中,并显示 strch

yxstrchattr

移动到位置 y,x 在窗口中,并显示 strch ,使用属性 attr

属性允许以突出显示的形式显示文本,如黑体、下划线、反转代码或彩色。它们将在下一小节中得到更详细的解释。

这个 addstr() 方法以python字符串或bytestring作为要显示的值。字节字符串的内容按原样发送到终端。使用窗口的值将字符串编码为字节 encoding 属性;这默认为返回的默认系统编码 locale.getpreferredencoding() .

这个 addch() 方法采用的字符可以是长度为1的字符串、长度为1的字节字符串或整数。

为扩展字符提供常量;这些常量是大于255的整数。例如, ACS_PLMINUS 是一个+/-符号,并且 ACS_ULCORNER 是框的左上角(便于绘制边框)。您也可以使用适当的Unicode字符。

Windows记住上次操作后光标的位置,因此,如果您忽略了 y,x 坐标、字符串或字符将显示在最后一个操作停止的位置。也可以使用 move(y,x) 方法。因为有些终端总是显示一个闪烁的光标,所以您可能希望确保光标位于某个不会分散注意力的位置;如果光标在某个明显随机的位置闪烁,可能会令人困惑。

如果应用程序根本不需要闪烁的光标,则可以调用 curs_set(False) 让它隐形。为了与旧的Curses版本兼容,有一个 leaveok(bool) 函数的同义词 curs_set() .什么时候? bool 如果是真的,Curses库将尝试抑制闪烁的光标,您不必担心将其保留在奇数位置。

属性和颜色

字符可以以不同的方式显示。基于文本的应用程序中的状态行通常显示在反向视频中,或者文本查看器可能需要突出显示某些单词。Curses通过允许您为屏幕上的每个单元格指定一个属性来支持这一点。

属性是一个整数,每一位代表一个不同的属性。您可以尝试显示设置了多个属性位的文本,但是curses不能保证所有可能的组合都可用,或者它们在视觉上都是不同的。这取决于所使用的终端的能力,所以最安全的做法是使用这里列出的最常用的属性。

属性

描述

A_BLINK

闪烁文本

A_BOLD

超亮或粗体文本

A_DIM

半亮文本

A_REVERSE

反转视频文本

A_STANDOUT

可用的最佳突出显示模式

A_UNDERLINE

下划线

因此,要在屏幕的顶行显示反向视频状态行,可以对其进行编码:

stdscr.addstr(0, 0, "Current mode: Typing mode",
              curses.A_REVERSE)
stdscr.refresh()

Curses库还支持提供颜色的终端上的颜色。最常见的此类终端可能是Linux控制台,然后是color xterms。

要使用颜色,必须调用 start_color() 调用后不久函数 initscr() ,初始化默认颜色集 curses.wrapper() 函数自动执行此操作)。完成后, has_colors() 如果正在使用的终端可以实际显示颜色,则函数返回true。(注:curses使用的是美国拼法“颜色”,而不是加拿大/英国拼法“颜色”。如果你习惯了英国拼法,你就得为这些功能而把它拼错。)

Curses库维护有限数量的颜色对,其中包含前景(或文本)颜色和背景色。您可以使用 color_pair() 函数;这可以与其他属性(如 A_REVERSE 但同样,这种组合不能保证在所有终端上都能工作。

使用颜色对1显示文本行的示例:

stdscr.addstr("Pretty text", curses.color_pair(1))
stdscr.refresh()

如我之前所说,颜色对由前景色和背景色组成。这个 init_pair(n, f, b) 函数更改颜色对的定义 n ,到前景色f和背景色b。颜色对0硬连线到黑色的白色,不能更改。

颜色编号,以及 start_color() 激活颜色模式时初始化8种基本颜色。它们是:0:黑色,1:红色,2:绿色,3:黄色,4:蓝色,5:洋红,6:青色和7:白色。这个 curses 模块为每种颜色定义命名常量: curses.COLOR_BLACKcurses.COLOR_RED 等等。

让我们把所有这些放在一起。要在白色背景上将颜色1更改为红色文本,您可以调用:

curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)

更改颜色对时,已使用该颜色对显示的任何文本都将更改为新颜色。您还可以使用以下颜色显示新文本:

stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))

非常漂亮的终端可以将实际颜色的定义更改为给定的RGB值。这允许您将颜色1(通常为红色)更改为紫色或蓝色或任何其他您类似于的颜色。不幸的是,Linux控制台不支持这一点,所以我无法尝试,也无法提供任何示例。您可以通过拨打 can_change_color() ,返回 True 如果有能力的话。如果你有幸拥有这样一个才华横溢的终端,请查阅系统的手册页了解更多信息。

用户输入

C Curses库只提供非常简单的输入机制。 Python curses 模块添加了一个基本的文本输入小部件。(其他类库,如 Urwid 拥有更多的小部件集合。)

从窗口获取输入有两种方法:

  • getch() 刷新屏幕,然后等待用户点击某个键,如果 echo() 已经提前打过调用了。可以选择指定暂停前光标应移动到的坐标。

  • getkey() 执行相同的操作,但将整数转换为字符串。单个字符返回为1个字符的字符串,特殊键(如功能键)返回包含键名的较长字符串,如 KEY_UP^G .

可以不等待用户使用 nodelay() 窗口法。后 nodelay(True)getch()getkey() 因为Windows是不堵的。表示没有输入准备就绪, getch() 返回 curses.ERR (值为-1)和 getkey() 引发异常。还有一个 halfdelay() 函数,可用于(实际上)在每个函数上设置计时器 getch() ;如果在指定的延迟(以十分之一秒为单位)内没有可用的输入,则curses会引发异常。

这个 getch() 方法返回一个整数;如果它在0到255之间,则表示按下键的ASCII代码。大于255的值是特殊键,如page up、home或光标键。您可以比较返回到常量的值,例如 curses.KEY_PPAGEcurses.KEY_HOMEcurses.KEY_LEFT . 程序的主循环可能如下所示:

while True:
    c = stdscr.getch()
    if c == ord('p'):
        PrintDocument()
    elif c == ord('q'):
        break  # Exit the while loop
    elif c == curses.KEY_HOME:
        x = y = 0

这个 curses.ascii 模块提供采用整数或1个字符字符串参数的ASCII类成员身份函数;这些参数在为此类循环编写更可读的测试时可能很有用。它还提供转换函数,这些函数接受整数或1个字符串参数,并返回相同的类型。例如, curses.ascii.ctrl() 返回与其参数对应的控制字符。

还有一种方法可以检索整个字符串, getstr() . 因为它的功能非常有限,所以很少使用它;唯一可用的编辑键是退格键和回车键,这两个键终止了字符串。它可以选择限制为固定字符数。::

curses.echo()            # Enable echoing of characters

# Get a 15-character string, with the cursor on the top line
s = stdscr.getstr(0,0, 15)

这个 curses.textpad 模块提供了一个文本框,它支持一组Emacs类的键绑定。各种方法的 Textbox 类支持使用输入验证进行编辑,并收集带有或不带有尾随空格的编辑结果。举个例子:

import curses
from curses.textpad import Textbox, rectangle

def main(stdscr):
    stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")

    editwin = curses.newwin(5,30, 2,1)
    rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
    stdscr.refresh()

    box = Textbox(editwin)

    # Let the user edit until Ctrl-G is struck.
    box.edit()

    # Get resulting contents
    message = box.gather()

请参阅上的库文档 curses.textpad 了解更多详细信息。

更多信息

此howto不包括一些高级主题,例如读取屏幕内容或从xterm实例捕获鼠标事件,而是 curses 模块现在相当完整。接下来你应该浏览一下。

如果您对Curses函数的详细行为有疑问,请参阅手册页了解Curses实现,无论它是ncurses还是专有的Unix供应商。手册页将记录任何异常,并提供所有函数、属性和 ACS_* 您可以使用的字符。

因为Curses API太大了,所以在Python接口中不支持某些函数。这通常不是因为它们很难实现,而是因为还没有人需要它们。另外,python还不支持与ncurse关联的菜单库。欢迎使用为这些添加支持的补丁;请参阅 the Python Developer's Guide 了解更多关于向python提交补丁的信息。

  • Writing Programs with NCURSES <http://invisible-island.net/ncurses/ncurses-intro.html> _ C程序员的长篇教程。

  • The ncurses man page

  • The ncurses FAQ

  • "Use curses... don't swear" <https://www.youtube.com/watch?v=eN1eZtjLEnU> _:Pycon 2013关于使用Curses或Urwid控制终端的视频。

  • "Console Applications with Urwid" <http://www.pyvideo.org/video/1568/console-applications-with-urwid> _:Pycon CA 2012演讲视频,演示了使用URWID编写的一些应用程序。