Skip to main content

Python使用Python编程

Author:

上午。 Kuchling,Eric S. Raymond

Release:

2.04

抽象

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

什么是诅ses?

curses库为基于文本的终端提供终端独立的画面绘制和键盘处理设施;这样的终端包括VT100,Linux控制台和由各种程序提供的模拟终端。显示终端支持各种控制代码,以执行常见操作,如移动光标,滚动屏幕和擦除区域。不同的终端使用广泛不同的代码,并且经常具有它们自己的轻微怪癖。

在图形显示的世界中,人们可能会问“为什么要麻烦”?这是真的,字符单元显示终端是一个过时的技术,但有一些壁龛,能够做与他们的奇怪的东西仍然是有价值的。一个利基在于没有运行X服务器的小占用或嵌入式Unix。另一种是诸如OS安装程序和内核配置器之类的工具,可能必须在任何图形支持可用之前运行。

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

curses库最初是为BSD Unix编写的;来自AT&T的后来的System V版本的Unix添加了许多增强功能和新功能。 BSD curses不再维护,已被ncurses替代,ncurses是AT&T接口的开源实现。如果你使用开源的Unix如Linux或FreeBSD,你的系统几乎肯定使用ncurses。由于大多数当前的商业Unix版本是基于System V代码的,所以这里描述的所有功能可能都可用。一些专有Unix的旧版本的诅咒可能不支持一切,虽然。

Python的Windows版本不包括 curses 模块。可以使用名为 UniCurses 的移植版本。您还可以尝试由Fredrik Lundh编写的 控制台模块,它不使用与curses相同的API,但提供了光标可寻址的文本输出和完全支持鼠标和键盘输入。

Python curses模块

你的Python模块是由curses提供的C函数的一个相当简单的包装;如果你已经熟悉C中的curses编程,那么将知识传递给Python真的很容易。最大的区别是,Python接口通过将不同的C函数(如 addstr()mvaddstr()mvwaddstr())合并到单个 addstr() 方法中,从而使事情变得更简单。稍后将更详细地介绍这一点。

这个HOWTO是用curses和Python编写文本模式程序的简介。它不试图成为curses API的完整指南;为此,请参阅Python库指南的ncurses部分,以及ncurses的C手册页。但是,它会给你的基本想法。

启动和结束curses应用程序

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

import curses
stdscr = curses.initscr()

通常诅咒应用程序关闭键自动回显到屏幕,以便能够读取键,并且只在特定情况下显示它们。这需要调用 noecho() 功能。

curses.noecho()

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

curses.cbreak()

终端通常返回特殊键,例如光标键或导航键,例如Page Up和Home,作为多字节转义序列。虽然你可以编写你的应用程序期望这样的序列,并相应地处理它们,诅咒可以为你做,返回一个特殊的值,如 curses.KEY_LEFT。要得到诅ses来做这项工作,你必须启用键盘模式。

stdscr.keypad(True)

终止curses应用程序比启动一个更容易。你需要打电话:

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

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

curses.endwin()

调试curses应用程序时的一个常见问题是,当应用程序死机时,将终端弄乱,而不将终端恢复到之前的状态。在Python中,这通常发生在你的代码是buggy并引发一个未捕获的异常。例如,当您输入键时,键不再回显到屏幕,这使得使用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。一旦callable返回,wrapper() 将恢复终端的原始状态。在 try ... except 内调用可调用方,捕获异常,恢复终端的状态,然后重新引发异常。因此,您的终端不会在异常时处于滑稽状态,您将能够读取异常的消息和回溯。

Windows和Pads

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

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

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

注意,在诅ses中使用的坐标系是不寻常的。坐标始终以 y,x 的顺序传递,窗口的左上角是坐标(0,0)。这打破了处理 x 坐标首先的坐标的常规约定。这是与大多数其他计算机应用程序的不幸的差异,但它是诅咒的一部分,因为它是第一次写,现在太晚了,现在改变。

您的应用程序可以通过使用 curses.LINEScurses.COLS 变量来确定屏幕的大小,以获得 yx 大小。法律坐标将从 (0,0) 扩展到 (curses.LINES - 1, curses.COLS - 1)

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

这是因为诅咒最初写的是慢的300波特终端连接;与这些端子,最小化重绘屏幕所需的时间是非常重要的。相反,curses会累积对屏幕的更改,并在调用 refresh() 时以最有效的方式显示它们。例如,如果您的程序在窗口中显示一些文本,然后清除窗口,则无需发送原始文本,因为它们从不可见。

在实践中,明确告诉诅咒重新绘制窗口并不会使编程更加复杂。大多数程序进入一连串的活动,然后暂停等待按键或用户的一些其他动作。所有你需要做的是确保屏幕已经重新绘制之前,暂停等待用户输入,通过首先调用 stdscr.refresh() 或一些其他相关窗口的 refresh() 方法。

垫是窗的特殊情况;它可以大于实际的显示屏幕,并且一次只显示一部分垫。创建垫需要垫的高度和宽度,而刷新垫需要给出屏幕区域的坐标,其中将显示垫的子部分。

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)在焊盘上。除了这个区别,垫完全像普通窗口,并支持相同的方法。

如果你有多个窗口和垫在屏幕上有一个更有效的方式更新屏幕和防止烦人的屏幕闪烁,因为屏幕的每个部分都更新。 refresh() 实际上做了两件事:

  1. 调用每个窗口的 noutrefresh() 方法来更新表示屏幕所需状态的底层数据结构。

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

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

显示文本

从C程序员的角度来看,curses有时可能看起来像一个扭曲的函数迷宫,所有的微妙的不同。例如,addstr()stdscr 窗口中的当前光标位置处显示字符串,而 mvaddstr() 在显示字符串之前首先移动到给定的y,x坐标。 waddstr() 就像 addstr(),但允许指定一个窗口,而不是默认使用 stdscrmvwaddstr() 允许指定窗口和坐标。

幸运的是,Python接口隐藏了所有这些细节。 stdscr 是像任何其他的窗口对象,并且诸如 addstr() 的方法接受多个参数形式。通常有四种不同的形式。

形成

描述

strch

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

strchattr

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

yxstrch

移动到窗口内的位置 y,x,并显示 strch

yxstrchattr

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

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

addstr() 方法将Python字符串或bytestring作为要显示的值。将bytestrings的内容原样发送到终端。使用窗口的 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 为true时,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控制台,其次是颜色xterms。

要使用颜色,您必须在调用 initscr() 后立即调用 start_color() 函数,以初始化默认颜色集(curses.wrapper() 函数自动执行此操作)。一旦完成,has_colors() 函数返回TRUE,如果正在使用的终端实际上可以显示颜色。 (注意:curses使用美国拼写’color’,而不是加拿大/英国拼写的’color’。如果你习惯英国的拼写,你就必须辞职,为了这些功能的拼写错误。 )

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() 变为非阻塞。为了表示没有输入就绪,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 模块提供ASCII类隶属函数,它接受整数或1个字符的字符串参数;这些可能有助于为这样的循环编写更可读的测试。它还提供转换函数,它接受整数或1个字符的字符串参数,并返回相同的类型。例如,curses.ascii.ctrl() 返回与其参数对应的控制字符。

还有一个方法来检索整个字符串,getstr()。它不经常使用,因为它的功能非常有限;唯一可用的编辑键是退格键和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)

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 模块的Python库页面现在已经相当完整。你应该浏览它下一步。

如果您对curses函数的详细行为有疑问,请参考curses实现的手册页,无论是ncurses还是专有的Unix供应商。手册页将记录任何怪癖,并提供您可用的所有功能,属性和 ACS_* 字符的完整列表。

因为curses API如此之大,一些函数在Python接口中不受支持。通常这不是因为它们难以实现,而是因为没有人需要它们。此外,Python还不支持与ncurses相关联的菜单库。欢迎补充支持这些的补丁;请参阅 Python开发人员指南 以了解有关向Python提交修补程序的更多信息。