终端开发补充 : 读 curses模块官方文档...

Posted 内脏坏了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了终端开发补充 : 读 curses模块官方文档...相关的知识,希望对你有一定的参考价值。

curses是一个提供终端屏幕打印和键盘处理的库, 我个人的理解就是终端里的gui(当然它是基于文本的)... 写2048的时候用到了这个库, 所以现在过来好好研究一下这个库...

下面是文档内容 :

首先在你做任何事之前, 你必须先调用 initscr() 初始化curses, 这个函数主要的作用是决定当前终端的类型, 然后发送一些必要的设置给终端, 并且创建独立的内部数据结构. 如果成功初始化的话, 该函数会返回一个代表屏幕的对象, 我们通常称为 stdscr (C语言就有的惯例). 另一方面, 正常情况下, 我们在终端上打字时, 你输入一个a终端上就会出现一个a, 但是对于curses编程来说这往往是没有必要的, 我们可以用 noecho() 来关掉它. 同时我们也需要按键之后得到立即响应而不是傻傻地回车之后再响应, 所以我们需要把输入模式改成 cbreak 模式而不是通常的缓冲输入模式, 这需要调用 cbreak() . 同时, 我们输入过程中有很多特别的键位, 比如方向键上下左右等等, 我们需要特殊处理这些键位的话, 我们可以调用 keypad(True), 这样的话, 按下左键位将返回一个类似 KEY_LEFT 的特殊值.  如果你需要结束终端的话, 只需要把你上面对终端进行的定制化设置关闭即可 :

import curses

#开启
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)

#关闭, 回复正常终端
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()

 

但是这样其实是有问题的, 当遇到程序半途出现异常然后游戏直接非正常关闭的情况时, 这样会使得终端变得很奇怪... 所以python中加入了另外一个函数来帮助我们解决这个问题, 也就是 wrapper(), 该函数需要一个可调用作为参数, 并完成上述的初始化过程,  同时初始化颜色如果当前终端提供了对颜色的支持的话, 一旦调用对象返回, 该函数会自动重载调用前的终端状态, 因为该函数的内部使用try-catch 实现的, 它在出现异常时也会先回复终端, 然后再次生成异常, 所以在解决了终端问题的同时仍然正常打印异常信息...

 1 from curses import wrapper
 2 
 3 def main(stdscr):
 4     # Clear screen
 5     stdscr.clear()
 6 
 7     # This raises ZeroDivisionError when i == 10.
 8     for i in range(0, 11):
 9         v = i-10
10         stdscr.addstr(i, 0, 10 divided by {} is {}.format(v, 10/v))
11 
12     stdscr.refresh()
13     stdscr.getkey()
14 
15 wrapper(main)

 

在curses中, 窗口对象是最基本的抽象, 它代表长方形的屏幕区域, 支持显示文本, 文本擦除 以及用户输入. 之前提到的 stdscr 就是一个窗口对象, 它代表的是整个屏幕. 但单窗口并不是任何时候都能满足人们的需求, 所以我们可以用 newwin() 来获得一个新的窗口对象 :

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

需要注意的是, 传入的第三个和第四个参数是先y再x, 这是历史遗留原因.

 

你可以用curses.LINEScurses.COLS 来获得你屏幕的y 和 x 大小... 同时如果你调用方法擦除屏幕的话屏幕并不会立即擦除, 必须要在你调用refresh()之后才行, 设计成这样当然也是有历史原因的, 早期的终端为了避免不必要的打印(你显示某些文字, 之后又显示另外的文字, 那么之前的文字可以直接不用显示了), 设计成了这个样子.

 

pad 是一个特殊的window, 他可以比实际屏幕更大, 一次只显示一部分的屏幕, 创建pad只需要高度和宽度, 但pad对象调用刷新的时候需要特别的参数 : 

 1 pad = curses.newpad(100, 100)
 2 # These loops fill the pad with letters; addch() is
 3 # explained in the next section
 4 for y in range(0, 99):
 5     for x in range(0, 99):
 6         pad.addch(y,x, ord(a) + (x*x+y*y) % 26)
 7 
 8 # Displays a section of the pad in the middle of the screen.
 9 # (0,0) : coordinate of upper-left corner of pad area to display.
10 # (5,5) : coordinate of upper-left corner of window area to be filled
11 #         with pad content.
12 # (20, 75) : coordinate of lower-right corner of window area to be
13 #          : filled with pad content.
14 pad.refresh( 0,0, 5,5, 20,75)

 

如果你有许多的窗口对象(都需要刷新), 为了避免不必要的闪烁, 你可以先对各个需要刷新的窗口调用 noutrefresh(), 它将升级内在的数据结构使之匹配你所要的内容, 然后统一调用 doupdate() 来刷新屏幕.

 

显示文本 :

C语言中的curses库中有很多关于文本打印的函数, 功能上只有细微的区别, 这导致该库显得凌乱而复杂,  在python中这些函数被封装在 addstr() 这个函数中, 该函数有4种形式, 参数以及用法如图 :

FormDescription
str or ch Display the string str or character ch at the current position
str or chattr Display the string str or character ch, using attribute attr at the current position
yxstr or ch Move to position y,x within the window, and display str or ch
yxstr or chattr Move to position y,x within the window, and display str or ch, using attribute attr

 

attr 可以用来指定显示文本的粗体, 斜体, 反向以及颜色等等, 之后会具体解释... 该函数接受一个string 或者 bytesstring, 然后使用窗口的encoding属性编码成bytes,  encoding属性默认为系统编码, 可以用 locale.getpreferredencoding() 来查询, 我的返回值是utf-8.

 

addch()接受一个长度为1的string或者bytesstring 或者一个整数. 指的注意的是该函数中整数超出255的将作为拓展字符, 例如 :

stdscr.addch(curses.ACS_PLMINUS)
stdscr.addch(curses.ACS_ULCORNER)

# >>> ±┌

 

每一次操作完, 窗口对象都会记住光标上次操作的位置, 你也可以是使用move(y, x)来移动光标, 同时你可以使用 curses.curs_set(False)来关闭光标显示,  在一些较为古老的curses的版本中, 也用 leaveok(bool)达到相同的效果. (亲测无效...)

 

属性与颜色 :

属性和颜色可以以不同的方式显示出来, 属性可以是一个值或者是一个集合(不知道怎么弄成一个集合的形式), 但并不能保证所有的效果都能成功的表现出来, 这取决于你的终端, 下面是一些相对来说比较保险的属性...

AttributeDescription
A_BLINK Blinking text
A_BOLD Extra bright or bold text
A_DIM Half bright text
A_REVERSE Reverse-video text
A_STANDOUT The best highlighting mode available
A_UNDERLINE Underlined text

比如这样你可以显示出一个类似vim状态栏的东西 :

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

 

为了使用不同的颜色, 你必须在调用 initscr() 之后调用 start_color(), 当然如果你使用的是 curses.wrapper(), 那么这个函数会自动地调用, 一旦这些完成了, 你可以调用has_colors()函数来确认你的是否的你的终端可以表现颜色(可以的话将返回True), 库中有多对颜色对, 每一对中都有对应的前景色和背景色, 你可以使用得到相应的颜色属性通过 color_pair() 函数, 你可以用 bitwise-or 与其他属性比如A_REVERSE 一起使用, 但不确保一定有效.

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

 

就像之前说过的, 每一个颜色对都有前景色和背景色, 我们可以用 init_pair(n, f, b) 来改变第n个颜色对的颜色, 当然第0对是无法改写的, 它将黑色写在白色的背景上. start_color() 初始化8中基本的颜色, 他们分别是 : 0:black, 1:red, 2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, and 7:white.  在curses这些颜色常数被定义为了curses.COLOR_BLACKcurses.COLOR_RED ...

curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
#When you change a color pair, any text already displayed using that color pair 
#will change to the new colors. You can also display new text in this color with:
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))

 

用户输入 :

C语言的curses库只是提供了非常简单的输入方法, 在python中这个模块加上了一个基本文本输入的玩意(widget 实在不知道怎么翻译了...) , 简单来说有2种 :

1. getch()  刷新屏幕等待用户敲击一个键, 然后如果echo() 之前被调用过得话, 这个键将被显示在屏幕上, 你也可以选择具体设置一个坐标作为光标应该移动的位置.

2. getkey() 做基本相同的事除了它返回的是字符串而不是int, 单字符返回单字符字符串, 特殊字符比如功能键返回更长的字符串包含这个按键的名字比如 ‘KEY_UP‘或者‘^G‘...

当然用户可以调用 nodelay() 在调用 nodelay(True)之后, getch() 和 getkey() 在这个窗口变成了非堵塞, 如果没有输入字符已经准备好, 那么 getch() 将返回 curses.ERR(-1) getkey() 将抛出异常, 也有一个 halfdelay() 函数, 他可以作为一个计时器使用, 这使得getch()具有如下特征 : 如果在给定时间内没有输入的话, 抛出异常... (时间的单位是十分之一秒)... (我试验的结果是返回-1而不是抛出异常), 而且这个东西是curses的一个方法而不是窗口对象的方法, 它其实是开启了一种模式, 和cbreak一样, 但是有一个等待时间.

getch()一般来讲会返回一个0到255的整数这代表你按下的键相对于的ascii码, 大于255的一般是上下左右的功能键(curses.KEY_PPAGEcurses.KEY_HOME, or 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 = 0

 

还有一段关于curses.ascii的, 感觉不是很重要就不翻译了, 原文 :

The curses.ascii module supplies ASCII class membership functions that take either integer or 1-character string arguments; these may be useful in writing more readable tests for such loops. It also supplies conversion functions that take either integer or 1-character-string arguments and return the same type. For example, curses.ascii.ctrl() returns the control character corresponding to its argument.

 

然后还有一个区的字符串的方法是 getstr(), 用的不是很多, 因为他只支持退格和回车两个编辑键位, 退格用来退格, 回车用来表示输入完毕, 下例的意思是只能输入一个小于等于15个字符的字符串, 光标从(0, 0)开始.

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的键位绑定 :

import curses
from curses.textpad import Textbox, rectangle

def main(stdscr):
    curses.use_default_colors()
    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.wrapper(main)

 

这只是python的HOWTO指南, 很加详细的信息可以直接看 https://docs.python.org/3.5/library/curses.html#module-curses ... 看了几个小时终于看完了...

以上是关于终端开发补充 : 读 curses模块官方文档...的主要内容,如果未能解决你的问题,请参考以下文章

Python Curses

Python Curses

第0001题 : 产生随机数(顺便读random模块官方文档)

Linux程序设计(搭建开发环境--curses)

Setupterm 找不到终端,在 Python 程序中使用 curses

使curses程序输出在程序退出后保留在终端回滚历史记录中