ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python curses 入门指南:基于 CPython 的终端文本界面开发实战

Python curses 入门指南:基于 CPython 的终端文本界面开发实战 Python curses 入门指南基于 CPython 的终端文本界面开发实战【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文围绕 CPython 官方 HOWTO 文档 curses 编程指南 展开系统讲解如何借助标准库curses扩展模块编写终端文字模式text-mode界面程序。读完本文你将掌握 curses 应用的完整生命周期初始化、窗口管理、文本输出、属性与颜色、用户输入并能在 CPython 源码中找到这些行为的实现位置例如 Lib/curses/init.py 中的wrapper()与 Modules/_cursesmodule.c 中的 C 层封装。什么是 cursescurses 库为基于文本的终端提供与终端无关的屏幕绘制和键盘处理设施适用终端包括 VT100、Linux 控制台以及各种程序提供的模拟终端。显示终端支持多种控制码来执行常见操作如移动光标、滚动屏幕、擦除区域等。不同终端使用差异很大的控制码且往往各有自己的小怪癖——curses 的价值正是屏蔽这些差异。在图形界面盛行的时代你可能会问还有必要吗。字符单元格终端虽是老旧技术但仍有值得发挥利处的场景小型化或嵌入式 Unix 系统这些环境不运行 X 服务器系统安装程序、内核配置工具等它们必须在任何图形支持可用之前运行。curses 库提供的功能相当基础它向程序员呈现一个抽象的显示区其中包含多个互不重叠的文本窗口。窗口内容可通过多种方式修改——添加文本、擦除、改变外观——curses 库会自行计算出需要向终端发送哪些控制码以产生正确输出。curses 本身不提供按钮、复选框、对话框等用户界面概念如果需要这类特性可以考虑 Urwid 等界面库文档中提及的第三方包需自行安装。从历史演进看curses 最初为 BSD Unix 编写后来 ATT 的 System V 系列 Unix 增加了大量增强和新函数。BSD curses 已不再维护取而代之的是 ncurses——一个开源的 ATT 接口实现。如果你使用的是 Linux 或 FreeBSD 等开源 Unix系统几乎可以确定使用 ncurses由于大多数当前商业 Unix 基于 System V 代码本文描述的功能大概率均可用只是某些专有 Unix 自带的旧版 curses 可能无法支持全部特性。平台限制Windows 版 Python 不包含curses模块第三方包 windows-curses 可在 Windows 上提供相同接口此为文档提及的第三方包非本仓库内容。Python curses 模块是 C 函数的薄封装Python 模块是对 curses 提供的 C 函数的相当简单的封装。如果你已经熟悉 C 语言 curses 编程把知识迁移到 Python 非常容易。最大的区别是Python 接口通过合并不同的 C 函数来简化编程例如把 C 的addstr、mvaddstr、mvwaddstr合并成单一的window.addstr()方法——这一点后文会详细展开。官方文档明确说明这份 HOWTO 不试图成为 curses API 的完整参考完整 API 请查阅 Python 库参考中的 curses 一节Doc/library/curses.rst以及 ncurses 的 C 手册页本文继承该 HOWTO 的全部核心内容并补充 CPython 源码级佐证。启动与结束 curses 应用初始化initscr、noecho、cbreak 与 keypad在做任何事情之前必须先初始化 curses。这通过调用initscr()函数完成它会探测终端类型、向终端发送必要的设置码、创建各种内部数据结构。成功时返回一个表示整个屏幕的窗口对象出于对应当前 C 变量名的习惯通常将其命名为stdscrimport curses stdscr curses.initscr()curses 应用通常会关闭按键的自动回显noecho以便能够读取按键并在特定条件下才显示它们curses.noecho()应用通常还需要对按键立即作出反应而不必等待按 Enter 键这就是所谓的 cbreak 模式相对于通常的缓冲输入模式curses.cbreak()终端通常将光标键、Page Up、Home 等特殊键返回为多字节转义序列。虽然你也可以让应用自行解析这些序列但 curses 可以代劳它会返回如curses.KEY_LEFT这样的特殊值。要启用这个功能需要打开 keypad 模式stdscr.keypad(True)源码佐证在 CPython 中curses.initscr()并非直接透传到 C 层。Lib/curses/init.py 中 Python 层的initscr()会先调用setupterm(termos.environ.get(TERM, unknown), fdsys.__stdout__.fileno())——因为setupterm在出错时抛出异常而 C 层的initscr在错误情况下可能直接exit()。随后它再调用 C 层_curses.initscr()并把ACS_*、WACS_*常量以及LINES、COLS从_curses模块拷贝到curses包命名空间——这些常量只在终端初始化完成后才存在所以文档特别提示如果你需要使用ACS_*常量不要写from curses import *。结束应用恢复终端状态结束一个 curses 应用比启动它容易得多。你需要调用curses.nocbreak() stdscr.keypad(False) curses.echo()来撤销那些curses 友好的终端设置然后调用endwin()将终端恢复到原始运行模式curses.endwin()调试陷阱与 curses.wrapper调试 curses 应用时的一个常见问题是当应用未恢复终端状态就死亡时终端会被搞乱。在 Python 中这常见于代码有 bug 并抛出未捕获异常时——比如你敲入的按键不再回显到屏幕使得操作 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会恢复终端的原始状态。该可调用对象运行在try...except实际是try...finally之中它会捕获异常、恢复终端状态、再重新抛出异常。因此即使发生异常终端也不会被留在奇怪的状态你可以正常阅读异常消息和 traceback。源码佐证Lib/curses/init.py 中wrapper(func, *args, **kwds)的实现印证了文档描述它依次执行initscr()、noecho()、cbreak()、stdscr.keypad(1)再try: start_color() except _curses.error: pass对无颜色终端无害随后return func(stdscr, *args, **kwds)finally块中依次执行stdscr.keypad(0)、echo()、nocbreak()、endwin()。另外注意wrapper会把额外的*args, **kwds透传给你的函数stdscr总是作为第一个参数传入。窗口Windows与垫片Pads窗口是 curses 的基本抽象。一个窗口对象表示屏幕上的一个矩形区域支持显示文本、擦除、允许用户输入字符串等方法。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的顺序传入且窗口左上角为坐标 (0,0)。这打破了先 x 后 y的常规坐标约定是多数其他计算机应用中不存在的差异——但不幸的是这是 curses 从最初诞生时就有的设计如今已无法更改。你的应用可以用curses.LINES和curses.COLS变量获取屏幕的y和x尺寸。合法坐标范围是从 (0,0) 到 (curses.LINES - 1, curses.COLS - 1)。当你调用显示或擦除文本的方法时效果不会立刻出现在屏幕上你必须调用窗口对象的refresh()方法来更新屏幕。这是因为 curses 最初是为 300 波特慢速终端连接设计的——在那种终端上最小化重绘屏幕所需时间非常重要。curses 会累积对屏幕的更改并在你调用refresh()时以最有效率的方式显示。例如如果你的程序在窗口中显示一些文本随后又清空该窗口就完全没有必要发送那些从未可见的原始文本。实践中显式告诉 curses 重绘窗口并不会让编程复杂多少。多数程序先忙一阵然后暂停等待按键或用户的其他操作你要做的只是保证在暂停等待用户输入之前先调用stdscr.refresh()或某个其他相关窗口的refresh()确保屏幕已经重绘。Pad可以比屏幕更大的窗口Pad 是窗口的特殊情况它可以比实际显示屏更大一次只显示 pad 的一部分。创建 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调用把 pad 上左上角为 (0,0) 的一个区域显示在屏幕坐标 (5,5) 到 (20,75) 的矩形内。除此之外pad 与普通窗口完全相同支持相同的方法。多窗口无闪烁刷新noutrefresh 与 doupdate如果你屏幕上同时有多个窗口和 pad有一种更高效的方式更新屏幕并避免各部分逐一刷新带来的恼人闪烁。refresh()实际上做了两件事调用每个窗口的noutrefresh()方法更新代表屏幕期望状态的底层数据结构调用doupdate()函数改变物理屏幕以匹配数据结构中记录的期望状态。因此你可以对多个窗口分别调用noutrefresh()更新数据结构然后只调用一次doupdate()来更新屏幕。显示文本从 C 程序员的视角看curses 有时像一个函数迷宫每个函数都略有不同addstr在stdscr窗口的当前光标位置显示字符串mvaddstr先移动到指定的 y,x 坐标再显示waddstr与addstr相同但允许指定使用哪个窗口而非默认的stdscrmvwaddstr则同时允许指定窗口和坐标。幸运的是Python 接口隐藏了所有这些细节。stdscr是和其他窗口对象一样的窗口对象其addstr等方法接受多种参数形式通常有四种形式描述str或ch在当前位置显示字符串str或字符chstr或ch,attr使用属性attr在当前位置显示str或chy,x,str或ch移动到窗口内y,x位置并显示str或chy,x,str或ch,attr移动到窗口内y,x位置使用属性attr显示str或ch属性允许以粗体、下划线、反色或彩色等醒目形式显示文本细节见下一小节。addstr接受 Python 字符串或字节串作为要显示的值字节串的内容原样发送到终端字符串则用该窗口的encoding属性编码为字节其默认值来自locale.getencoding()返回的系统默认编码。addch方法接受一个字符可以是长度为 1 的字符串、长度为 1 的字节串或一个整数。扩展字符如框线字符有对应常量这些常量是大于 255 的整数。例如ACS_PLMINUS是 /- 符号ACS_ULCORNER是盒子的左上角画边框时很好用。你也可以直接使用相应的 Unicode 字符。窗口会记住上次操作后光标停留的位置所以如果你省略y,x坐标字符串或字符就会显示在上次操作结束的地方。你也可以用move(y,x)方法移动光标。由于有些终端总是显示闪烁的光标你可能希望把光标定位到某个不引人注目的位置——光标在某个看似随机的位置闪烁会令人困惑。如果你的应用完全不需要闪烁光标可以调用curs_set(False)让它不可见。为兼容旧版 curses还有一个leaveok(bool)函数作为curs_set的同义词当bool为真时curses 库会尝试抑制闪烁光标你就不必担心把它留在奇怪的位置了。属性与颜色字符可以以不同方式显示文字型应用的状态行通常以反色reverse video显示文本查看器可能需要高亮某些词。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 控制台其次是彩色 xterm。要使用颜色必须在调用initscr()之后尽快调用start_color()函数来初始化默认颜色集curses.wrapper函数会自动做这件事。完成后has_colors()函数返回True文档原文为 TRUE当且仅当当前终端确实能显示颜色。注意curses 使用美式拼写 color 而非英式的 colour。curses 库维护有限个颜色对color pair每个颜色对包含前景色文本色和背景色。用color_pair()函数可获取颜色对对应的属性值它可以与其他属性如A_REVERSE按位或bitwise-OR但同样不保证在所有终端上有效。一个使用颜色对 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_BLACK、curses.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。如果你的终端这么强大请查阅系统的 man 页获取更多信息。源码佐证Lib/curses/init.py 中 Python 层还重写了start_color()调用 C 层后把此时才可用的COLORS与COLOR_PAIRS变量拷贝进curses包命名空间——这与initscr()拷贝LINES/COLS的思路一致。用户输入C curses 库只提供非常简单的输入机制Python 的curses模块在此之上增加了一个基础文本输入部件Urwid 等其他库有更完整的部件集合。从窗口获取输入有两种方法getch()先刷新屏幕然后等待用户按下一个键如果此前调用过echo()该键会被显示。可选地指定一个坐标在暂停前把光标移过去。getkey()做同样的事但把整数转换成字符串单个字符返回长度为 1 的字符串函数键等特殊键返回包含键名的较长字符串如KEY_UP或^G。用窗口的nodelay()方法可以避免等待用户。nodelay(True)之后该窗口的getch()和getkey()变成非阻塞的。为表示当前没有输入getch()返回curses.ERR值为 -1getkey()则抛出异常。此外还有halfdelay()函数可以等效地为每次getch()设置一个计时器如果在指定延迟以 0.1 秒为单位内没有输入就绪curses 抛出异常。getch()方法返回一个整数介于 0 到 255 之间时表示所按按键的 ASCII 码大于 255 的值是 Page Up、Home 或方向键等特殊键。你可以把返回值与curses.KEY_PPAGE、curses.KEY_HOME、curses.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 0curses.ascii模块提供接受整数或 1 字符字符串参数的 ASCII 类别判定函数在编写这类循环中更可读的测试时可能很有用它还提供同样接受整数或 1 字符字符串、并返回相同类型的转换函数。例如curses.ascii.ctrl()返回其参数对应的控制字符。源码佐证Lib/curses/ascii.py 中除isalpha、isdigit、isctrl等判定函数外还定义了NUL…DEL全部控制字符常量且每个函数内部通过_ctoi()同时兼容整数与字符串两种参数形态与文档描述一致。还有一个获取整行字符串的方法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 键位绑定集Emacs-like keybindings的文本框。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() main wrapper(main) # 或写成 wrapper(main)更多细节请见 Doc/library/curses.rst 中curses.textpad的库文档。源码佐证Lib/curses/textpad.py 中Textbox类与rectangle(win, uly, ulx, lry, lrx)函数用 ACS 盒线常量在窗口上画矩形边框正是上面示例所依赖的实现。进一步学习与仓库中的参考位置本 HOWTO 没有覆盖一些高级主题例如读取屏幕内容、捕获 xterm 实例中的鼠标事件等但 Doc/library/curses.rst 中curses模块的 Python 库参考页面已经相当完整建议接着浏览。如果对 curses 函数的详细行为拿不准请查阅你所用 curses 实现的 man 页无论是 ncurses 还是专有 Unix 厂商的版本手册页会记录各种怪癖并给出所有可用函数、属性以及完整ACS_*字符列表——ACS_*常量的参考标签见 Doc/library/curses.rst 中的 curses-acs-codes 小节。由于 curses API 非常庞大Python 接口中没有支持其中一些函数——通常不是实现困难而是尚无人需要另外 Python 目前也不支持 ncurses 附带的 menu 库。由于 curses API 如此之大Python 接口未覆盖部分函数欢迎提交补丁支持这些功能。在本仓库中可以继续深入的实现与验证位置位置说明Lib/curses/init.pyinitscr/newterm/start_color的 Python 包装与wrapper()的完整实现Lib/curses/textpad.pyTextbox、rectangle等文本输入部件实现Lib/curses/ascii.pyASCII 类别判定与转换函数Lib/curses/panel.py基于_curses_panel的 panel 扩展Modules/_cursesmodule.ccurses的 C 扩展实现封装 ncurses 库Lib/test/test_curses.pycurses 测试集注意顶部requires(curses)运行时需以-u curses提供 curses 资源且要求设置了可用的$TERM适用前提与限制小结curses 模块适用于 Unix 类系统Linux、FreeBSD、macOS 等 ncurses 环境Windows 版 CPython 不含该模块测试中 cygwin 因curses 基本会挂起而被跳过NetBSD/illumos 原生 curses 在旧版上对重复newterm()/delscreen()存在问题ncurses 6.5 之前——这些细节均可在 Lib/test/test_curses.py 头部注释中看到可作为你在不同发行版上排查行为的参考。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表