Python非阻塞非干扰式键盘按键检测。

3
我有一个循环,会执行一些工作并将大量信息打印到标准输出(stdout)。因为这是个循环,所以会一遍又一遍地执行。我想做的是,检测用户是否按下键盘上的某个按键(可以是箭头、回车或字母等),并在发生这种情况时执行一些工作。
本次子任务应该很简单,但我已经花了四个小时尝试不同的方法,几乎没有取得实质性进展。
这只需要在Linux系统中工作。
我能找到的最好方法类似于下面这样。但是它只能在0.05秒内捕获到按键。
import sys,tty,termios
class _Getch:
    def __call__(self, n=1):
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(n)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


def getch(timeout=0.2):
    inkey = _Getch()
    k = ''
    start_sec = time()
    while(time() - start_sec < timeout):
        if k == '':
            k = timeout_call(inkey, timeout_duration=timeout - (time() - start_sec))
    if k == u'\x1b':
        k += inkey(2)
        if k == u'\x1b[A':
            return "up"
        if k == u'\x1b[B':
            return "down"
        if k == u'\x1b[C':
            return "right"
        if k == u'\x1b[D':
            return "left"
    elif k == "q":
        return 'q'
    elif k == "\n":
        return 'enter'
    else:
        return None


while True:
    do_some_work_that_lasts_about_0_2_seconds()
    key = getch(0.05)
    if key:
        do_something_with_the(key)
2个回答

4

这个问题之前已经被问过了。有人发布了一个不错的、简短的、重构后的解决方案

以下是转载内容

import sys
import select
import tty
import termios

class NonBlockingConsole(object):

    def __enter__(self):
        self.old_settings = termios.tcgetattr(sys.stdin)
        tty.setcbreak(sys.stdin.fileno())
        return self

    def __exit__(self, type, value, traceback):
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)


    def get_data(self):
        if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
            return sys.stdin.read(1)
        return False


if __name__ == '__main__':
    # Use like this
    with NonBlockingConsole() as nbc:
        i = 0
        while 1:
            print i
            i += 1

            if nbc.get_data() == '\x1b':  # x1b is ESC
                break

一个问题:我该如何使用这种方法读取箭头键和普通键?箭头键似乎使用三个字符,但如果我使用return sys.stdin.read(3),那么控制台就不再是“非阻塞”的了。 - frnhr
我会接受你的答案,因为它指引了我正确的方向。谢谢。但对于我的情况来说,这并不是一个真正的解决方案,因为它不支持转义序列。select.select 对此似乎不足以胜任。 - frnhr

1

这是我想出的一个解决方案。不完美,因为它依赖于超时,并且有时只能捕获转义序列的一半,如果在超时到期前按下键,可能会出现这种情况。但这是我能想到的最好的解决方案。令人失望...

def timeout_call(func, args=(), kwargs=None, timeout_duration=1.0, default=None):
    if not kwargs:
        kwargs = {}
    import signal

    class TimeoutError(Exception):
        pass

    def handler(signum, frame):
        raise TimeoutError()

    # set the timeout handler
    signal.signal(signal.SIGALRM, handler)
    signal.setitimer(signal.ITIMER_REAL, timeout_duration)
    try:
        result = func(*args, **kwargs)
    except TimeoutError as exc:
        result = default
    finally:
        signal.alarm(0)

    return result


class NonBlockingConsole(object):

    def __enter__(self):
        self.old_settings = termios.tcgetattr(sys.stdin)
        tty.setcbreak(sys.stdin.fileno())
        return self

    def __exit__(self, type, value, traceback):
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)

    def get_data(self):
        k = ''
        while True:
            c = timeout_call(sys.stdin.read, args=[1], timeout_duration=0.05)
            if c is None:
                break
            k += c

        return k if k else False

使用方法:

with NonBlockingConsole() as nbc:
    while True:
        sleep(0.05)  # or longer, but not shorter, for my setup anyways...
        data = nbc.get_data()
        if data:
            print data.encode('string-escape')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接