是否有办法从用户输入中读取单个字符?例如,他们在终端上按下一个键,它就会返回(有点像getch())。我知道Windows中有这个功能,但我想要跨平台的东西。


当前回答

如果你只是想按住屏幕,这样你就可以在终端上看到结果

input()

在代码的末尾,它将保存屏幕

其他回答

这段代码,基于这里,将正确地引发KeyboardInterrupt和EOFError如果按下Ctrl+C或Ctrl+D。

应该在Windows和Linux上工作。OS X版本可从原始源代码获得。

class _Getch:
    """Gets a single character from standard input.  Does not echo to the screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): 
        char = self.impl()
        if char == '\x03':
            raise KeyboardInterrupt
        elif char == '\x04':
            raise EOFError
        return char

class _GetchUnix:
    def __init__(self):
        import tty
        import sys

    def __call__(self):
        import sys
        import tty
        import termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()

python中的curses包可以使用一些语句从终端进入“原始”模式进行字符输入。Curses的主要用途是接管屏幕进行输出,这可能不是你想要的。此代码片段使用print()语句代替,这是可用的,但必须注意curses如何更改附加到输出的行结束符。

#!/usr/bin/python3
# Demo of single char terminal input in raw mode with the curses package.
import sys, curses

def run_one_char(dummy):
    'Run until a carriage return is entered'
    char = ' '
    print('Welcome to curses', flush=True)
    while ord(char) != 13:
        char = one_char()

def one_char():
    'Read one character from the keyboard'
    print('\r? ', flush= True, end = '')

    ## A blocking single char read in raw mode. 
    char = sys.stdin.read(1)
    print('You entered %s\r' % char)
    return char

## Must init curses before calling any functions
curses.initscr()
## To make sure the terminal returns to its initial settings,
## and to set raw mode and guarantee cleanup on exit. 
curses.wrapper(run_one_char)
print('Curses be gone!')
sys.stdin.read(1)

基本上会从STDIN读取1个字节。

如果你必须使用不等待\n的方法,你可以按照之前的回答使用这段代码:

class _Getch:
    """Gets a single character from standard input.  Does not echo to the screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): return self.impl()


class _GetchUnix:
    def __init__(self):
        import tty, sys

    def __call__(self):
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()

(摘自http://code.activestate.com/recipes/134892/)

值得一试的是readchar库,它在一定程度上基于其他答案中提到的ActiveState配方(但从那以后已经有了很长的路要走)。

安装:

python -m pip install readchar

用法:

import readchar
print('Reading a char:')
print(repr(readchar.readchar()))
print('Reading a key:')
print(repr(readchar.readkey()))

使用Python 3.9在Windows和Linux上测试。

Windows和Linux之间的键码并不总是相同的,但是库提供了特定于平台的定义,如readchar.key。F1来帮忙。

由于Linux将大多数特殊键报告为转义序列(从\x1b开始),如果您按下实际的转义键(终端将其报告为单独的\x1b), readkey()就会混淆。不幸的是,这是一个常见的Unix问题,没有真正可靠的解决方案。

注意,当readkey在Ctrl+C上触发KeyboardInterrupt时(参见readchar.config),其他Linux信号键(例如Ctrl+D和Ctrl+Z)被捕获并返回(分别为'\x04'和'\x1a'),这可能是也可能不是可取的。

另一个回答中的一个评论提到了cbreak模式,这对Unix实现很重要,因为您通常不希望^C (KeyboardError)被getchar使用(当您将终端设置为原始模式时,它会被使用,就像大多数其他回答所做的那样)。

另一个重要的细节是,如果您希望读取一个字符而不是一个字节,您应该从输入流中读取4个字节,因为这是UTF-8 (Python 3+)中单个字符所包含的最大字节数。对于键盘箭头等多字节字符,只读取一个字节将产生意想不到的结果。

下面是我修改后的Unix实现:

import contextlib
import os
import sys
import termios
import tty


_MAX_CHARACTER_BYTE_LENGTH = 4


@contextlib.contextmanager
def _tty_reset(file_descriptor):
    """
    A context manager that saves the tty flags of a file descriptor upon
    entering and restores them upon exiting.
    """
    old_settings = termios.tcgetattr(file_descriptor)
    try:
        yield
    finally:
        termios.tcsetattr(file_descriptor, termios.TCSADRAIN, old_settings)


def get_character(file=sys.stdin):
    """
    Read a single character from the given input stream (defaults to sys.stdin).
    """
    file_descriptor = file.fileno()
    with _tty_reset(file_descriptor):
        tty.setcbreak(file_descriptor)
        return os.read(file_descriptor, _MAX_CHARACTER_BYTE_LENGTH)