如何在Python的curses库中使用扩展字符?

4 投票
3 回答
9666 浏览
提问于 2025-04-15 13:36

我最近在看关于Python中Curses编程的教程,很多教程提到可以使用扩展字符,比如画线的符号。这些字符的编码值大于255,而curses库知道如何在当前终端字体中显示它们。

有些教程说你可以这样使用:

c = ACS_ULCORNER

...还有一些说你应该这样用:

c = curses.ACS_ULCORNER

(这应该是一个框的左上角,看起来像一个上下翻转的L)

无论我用哪种方法,程序总是提示名称未定义,因此运行失败。我试过“import curses”和“from curses import *”,但都不行。

Curses的window()函数会用到这些字符,所以我甚至试着在我的电脑上找源代码,看看它是怎么做的,但我找了半天也没找到。

3 个回答

4

我觉得下面的内容和这个问题是相关的,可以发在这里。我将使用utfinfo.pl(也可以在Super User上查看)。

首先,对于标准的ASCII字符集,Unicode代码点和字节编码是一样的:

$ echo 'a' | perl utfinfo.pl 
Char: 'a' u: 97 [0x0061] b: 97 [0x61] n: LATIN SMALL LETTER A [Basic Latin]

所以我们可以在Python的curses中这样做:

window.addch('a')
window.border('a') 

...这样就能按预期工作了。

但是,如果一个字符超出了基本的ASCII范围,那么就会有一些不同之处,这一点在addch的文档中并没有明确说明。首先,我可以这样做:

window.addch(curses.ACS_PI)
window.border(curses.ACS_PI)

...在这种情况下,在我的gnome-terminal中,Unicode字符'π'会被显示出来。不过,如果你查看ACS_PI,你会发现它是一个整数,值为4194427(0x40007b);所以下面的代码也会显示同样的字符(或者说是字形?)'π':

window.addch(0x40007b)
window.border(0x40007b)

为了搞清楚发生了什么,我在ncurses的源代码中查找,发现了以下内容:

#define ACS_PI      NCURSES_ACS('{') /* Pi */  
#define NCURSES_ACS(c)  (acs_map[NCURSES_CAST(unsigned char,c)])
#define NCURSES_CAST(type,value) static_cast<type>(value)
#lib_acs.c: NCURSES_EXPORT_VAR(chtype *) _nc_acs_map(void): MyBuffer = typeCalloc(chtype, ACS_LEN);
#define typeCalloc(type,elts) (type *)calloc((elts),sizeof(type))
#./widechar/lib_wacs.c: { '{',  { '*',  0x03c0 }},  /* greek pi */

注意这里:

$ echo '{π' | perl utfinfo.pl 
Got 2 uchars
Char: '{' u: 123 [0x007B] b: 123 [0x7B] n: LEFT CURLY BRACKET [Basic Latin]
Char: 'π' u: 960 [0x03C0] b: 207,128 [0xCF,0x80] n: GREEK SMALL LETTER PI [Greek and Coptic]

...这两个都和ACS_PI的值4194427(0x40007b)没有关系。

因此,当addch和/或border遇到一个超出ASCII的字符(基本上是一个unsigned int,而不是unsigned char)时,它们(至少在这个例子中)并不是把这个数字当作Unicode代码点,或者UTF-8编码的字节表示,而是把它当作acs_map查找函数的索引(不过最终它确实会返回Unicode代码点,即使它模拟的是VT-100)。这就是为什么下面的代码:

window.addch('π') 
window.border('π') 

在Python 2.7中会失败,提示argument 1 or 3 must be a ch or an int;而在Python 3.2中则会直接显示一个空格,而不是字符。当我们指定'π'时,实际上我们指定的是UTF-8编码[0xCF,0x80] - 但即使我们指定Unicode代码点:

window.addch(0x03C0) 
window.border0x03C0) 

...在Python 2.7和3.2中都不会显示任何东西(空格)。

话虽如此,addstr函数确实可以接受UTF-8编码的字符串,并且工作正常:

window.addstr('π')

...但是对于边框来说,由于border()显然以和addch()相同的方式处理字符 - 我们显然没办法处理那些没有明确指定为ACS常量的字符(而且这些常量也不多)。

希望这对某些人有帮助,
祝好!

5

你需要把你的本地设置为“全部”,然后把输出编码成utf-8,方法如下:

import curses
import locale

locale.setlocale(locale.LC_ALL, '')    # set your locale

scr = curses.initscr()
scr.clear()
scr.addstr(0, 0, u'\u3042'.encode('utf-8'))
scr.refresh()
# here implement simple code to wait for user input to quit
scr.endwin()

输出结果: あ

4

来自 curses/__init__.py

有一些常量,特别是那些以 ACS_* 开头的常量,只有在调用了 initscr() 这个函数之后,才会被添加到 C 语言的 _curses 模块的字典里。(有些版本的 SGI 的 curses 在调用 initscr() 之前并不会给这些常量定义值。)这个包装函数会先调用底层的 C 语言的 initscr(),然后再把 _curses 模块中的常量复制到 curses 包的字典里。如果你需要使用 ACS_* 常量,最好不要用 'from curses import *' 这种方式。

换句话说:

>>> import curses
>>> curses.ACS_ULCORNER
exception
>>> curses.initscr()
>>> curses.ACS_ULCORNER
>>> 4194412

撰写回答