1

我有一个基本上有两个“线程”的重新设计的 python 诅咒代码。它们不是真正的线程——一个是主子窗口处理函数,第二个是不同的子窗口处理函数,在定时器上执行。我遇到了一个有趣的效果:

  • 主窗口代码正在使用 getstr() 等待用户输入。
  • 与此同时,定时器中断会到来,中断代码会在不同的子窗口中输出一些东西。
  • 计时器函数的输出将导致 getstr() 返回空输入。

什么可能导致这种影响?除了检查返回字符串之外,还有什么方法可以避免这种影响?


重现问题的示例代码:

#!/usr/bin/env python
# Simple code to show timer updates

import curses
import os, signal, sys, time, traceback
import math

UPDATE_INTERVAL = 2
test_bed_windows = []
global_count = 0

def signal_handler(signum, frame):
    global test_bed_windows
    global global_count

    if (signum == signal.SIGALRM):
        # Update all the test bed windows
        # restart the timer.
        signal.alarm(UPDATE_INTERVAL)
        global_count += 1

        for tb_window in test_bed_windows:
            tb_window.addstr(1, 1, "Upd: {0}.{1}".format(global_count, test_bed_windows.index(tb_window)))
            tb_window.refresh()
    else:
        print("unexpected signal: {0}".format(signam))
        pass

def main(stdscr):
    # window setup
    screen_y, screen_x = stdscr.getmaxyx()
    stdscr.box()

    # print version
    version_str = " Timer Demo v:0 "
    stdscr.addstr(0, screen_x - len(version_str) - 1, version_str)
    stdscr.refresh()

    window = stdscr.subwin(screen_y-2,screen_x-2,1,1)

    for i in range(3):
        subwin = window.derwin(3,12,1,2 + (15*i))

        test_bed_windows.append(subwin)
        subwin.box()
        subwin.refresh()

    signal.signal(signal.SIGALRM, signal_handler)
    signal.alarm(UPDATE_INTERVAL)

    # Output the prompt and wait for the input:
    window.addstr(12, 1, "Enter Q/q to exit\n")
    window.refresh()

    the_prompt = "Enter here> "
    while True:
        window.addstr(the_prompt)
        window.refresh()

        curses.echo()
        selection = window.getstr()
        curses.noecho()

        if selection == '':
            continue
        elif selection.upper() == 'Q':
            break
        else:
            window.addstr("Entered: {0}".format(selection))
            window.refresh()


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

2 回答 2

0

我怀疑不是写入子窗口导致 getstr() 返回一个空字符串,而是警报信号本身。(从信号处理程序中写入窗口可能也没有很好的定义,但这是一个单独的问题。)

Python 的 curses 模块通常建立在 C 库 Curses 之上,当任何信号(除了它内部处理的少数信号)进入时,它的大部分阻塞输入调用都会返回。在 C 中,有一个针对这种情况定义的 API(该函数返回 -1 并将 errno 设置为 EINTER)。

Python 模块表示,如果 curses 函数返回错误,它将引发异常。我不确定为什么在这种情况下它不这样做。

编辑:一个可能的解决方案是使用比 curses 更适合程序员的控制台 UI 库。Urwid出现(在我对手册的简短浏览中)支持事件驱动的 UI 更新(包括计时器更新),同时处理键盘输入。与处理信号和诅咒之间粗略且记录不充分的交互相比,学习这一点可能更容易。

编辑:来自getch()的手册页:

SVr4 和 XSI 诅咒文档中未指定在处理信号存在时 getch 和朋友的行为。在历史 curses 实现中,它取决于操作系统的处理信号接收实现是否中断正在进行的 read(2) 调用,并且(在某些实现中)取决于输入超时或非阻塞模式是否已经放。

关心可移植性的程序员应该为以下两种情况中的任何一种做好准备:(a)信号接收不会中断getch;(b) 信号接收中断 getch 并使其返回 ERR 并将 errno 设置为 EINTR。ncurses实现下,处理的信号永远不会中断getch

我尝试使用getch而不是getstr并且它确实在信号上返回 -1。如果由getstr实现,那(负返回值)将解决这个问题。所以现在的选项是(1)编写自己的带有错误处理的 getstr 或(2)使用Urwid。这可能是 Python 库的错误吗?

于 2012-07-04T00:15:15.467 回答
0

这仅供参考。因此,为了以最简单的方式解决这个问题,我只是编写了自己的函数来完成getstr()所做的事情和一些事情。它不会因错误而退出。欢迎所有评论,更正,优化。

'''
Function to read a string from the current cursor position.
It supports some simple editing: Ctrl+A, Ctrl+E, Backspace, Del, Home, End,
'''
def getstr(window, prompt = "> ", end_on_error = False):
    result = ""
    starty, startx = window.getyx()
    window.move(starty, 0)
    window.deleteln()
    window.addstr(prompt)
    window.refresh()
    window.keypad(True)

    starty, startx = window.getyx()
    endy, endx = window.getyx()
    maxy, maxx = window.getmaxyx()
    while True:
        try:
            selection = -1
            while (selection < 0 and end_on_error == False):
                selection = window.getch()
        except:
            e = sys.exc_info()[0]
            window.addstr("<p>Error: %s</p>" % e)
            break

        if (selection == curses.KEY_ENTER or selection == ord('\n')):
            break
        elif (selection == curses.KEY_HOME or selection == 1):
            window.move(starty, startx)
            continue
        elif (selection == curses.KEY_END or selection == 5):
            window.move(endy, endx)
            continue
        elif (selection == curses.KEY_DC):
            cy, cx = window.getyx()
            window.delch()
            result = result[:(cx - startx)] + result[(cx - startx + 1):]
            endx -= 1
            continue
        elif (selection == curses.KEY_LEFT):
            cy, cx = window.getyx()
            if (cx > startx):
                window.move(cy, cx-1)
            continue
        elif (selection == curses.KEY_RIGHT):
            cy, cx = window.getyx()
            if (cx < endx):
                window.move(cy, cx+1)
            continue
        elif (selection == curses.KEY_BACKSPACE or selection == 127):
            cy, cx = window.getyx()
            if (cx == startx):
                # no more to backspace
                continue
            else:
                window.move(cy, cx-1)
                window.delch()
                endx -= 1
                cx -= 1
                result = result[:(cx - startx)] + result[(cx - startx + 1):]
                continue
        else:
            endy, endx = window.getyx()
            if (selection < 256 and endx+1 < maxx):
                result = result[:(endx - startx)] + chr(selection) + result[(endx - startx):]
                window.addstr(result[(endx - startx):])
                window.move(endy, endx+1)
                endy, endx = window.getyx()


    window.keypad(False)
    output(result)
    return result
于 2012-07-07T00:33:49.760 回答