关于ncurses:Python诅咒困境

Python curses dilemma

我正在玩Python和诅咒。

我跑的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
import curses

def main():
    curses.initscr()
    curses.cbreak()
    for i in range(3):
        time.sleep(1)
        curses.flash()
        pass
    print("Hello World" )
    curses.endwin()

if __name__ == '__main__':
    main()

如果我一直等待,curses.endwin()被调用,所以一切正常。
但是,如果我用Ctrl-C缩短它,curses.endwin()永远不会被调用,所以它搞砸了我的终端会话。

处理这种情况的正确方法是什么? 我怎样才能确保无论我如何尝试结束/中断程序(例如Ctrl-C,Ctrl-Z),它都不会弄乱终端?


我相信你正在寻找curses.wrapper
请参阅http://docs.python.org/dev/library/curses.html#curses.wrapper

它将在init上执行curses.cbreak(),curses.noecho()和curses_screen.keypad(1)并在退出时反转它们,即使退出是异常。

您的程序作为包装器的函数,例如:

1
2
3
4
5
6
def main(screen):
   """screen is a curses screen passed from the wrapper"""
    ...

if __name__ == '__main__':
    curses.wrapper(main)


你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
def main():
    curses.initscr()

    try:
        curses.cbreak()
        for i in range(3):
            time.sleep(1)
            curses.flash()
            pass
        print("Hello World" )
    finally:
        curses.endwin()

或者更好的是,创建一个上下文包装器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CursesWindow(object):
    def __enter__(self):
        curses.initscr()

    def __exit__(self):
        curses.endwin()

def main():
    with CursesWindow():
        curses.cbreak()
        for i in range(3):
            time.sleep(1)
            curses.flash()
            pass
        print("Hello World" )


我的建议:出于测试目的,使用简单的包装器shell脚本调用脚本;让shell脚本执行reset命令,使终端设置恢复到可用状态:

1
2
3
4
#!/bin/sh
eval"$@"
stty -sane
reset

...称之为run.sh并且开心。如果您将参数作为命令输入,那么这应该像shell一样运行您的命令(更确切地说,如果您将参数包装在硬引号中)。

为了确保你的程序能够使终端处于健壮状态,面对未捕获的异常和异常终止......要么使用curses.wrapper()方法调用你的顶级入口点(可能main()或者您选择实施)或将您的代码包装在您自己的curses.*方法序列中以恢复光标可见性,恢复"cbreak"(规范/熟入输入)模式,恢复正常的"回声"设置以及您可能已经破解的任何其他内容用。

您还可以使用Python:atexit Handlers来注册所有清理操作。但是可能仍然存在无法调用代码的情况---某些非捕获信号以及调用os._exit()的任何情况。

即使在这些情况下,我的小shell脚本包装器也应该相当强大。


您可以:

  • 将代码包装在调用curses.endwin()try / finally块中
  • 通过signal库专门捕获中断信号
  • 使用atexit库。

对于基本情况,第一个选项可能是最简单的(如果你没有运行很多代码)。

第二个选项是最具体的,如果你想为Ctrl + C做一些特殊的事情。

如果您总是希望执行某些关闭操作,那么最后一个选项是最强大的,无论您的程序如何结束。


您需要捕获信号并在捕获期间运行endwin()

有关这方面的信息,请看这个答案:如何在Python中捕获SIGINT?