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


当前回答

这可能是上下文管理器的一个用例。不考虑Windows操作系统,以下是我的建议:

#!/usr/bin/env python3
# file: 'readchar.py'
"""
Implementation of a way to get a single character of input
without waiting for the user to hit <Enter>.
(OS is Linux, Ubuntu 14.04)
"""

import tty, sys, termios

class ReadChar():
    def __enter__(self):
        self.fd = sys.stdin.fileno()
        self.old_settings = termios.tcgetattr(self.fd)
        tty.setraw(sys.stdin.fileno())
        return sys.stdin.read(1)
    def __exit__(self, type, value, traceback):
        termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)

def test():
    while True:
        with ReadChar() as rc:
            char = rc
        if ord(char) <= 32:
            print("You entered character with ordinal {}."\
                        .format(ord(char)))
        else:
            print("You entered character '{}'."\
                        .format(char))
        if char in "^C^D":
            sys.exit()

if __name__ == "__main__":
    test()

其他回答

这是NON-BLOCKING,读取一个键并将其存储在keypress.key中。

import Tkinter as tk


class Keypress:
    def __init__(self):
        self.root = tk.Tk()
        self.root.geometry('300x200')
        self.root.bind('<KeyPress>', self.onKeyPress)

    def onKeyPress(self, event):
        self.key = event.char

    def __eq__(self, other):
        return self.key == other

    def __str__(self):
        return self.key

在你的程序中

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)

值得一试的是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)

两个答案中逐字引用的ActiveState配方是过度设计的。它可以归结为:

def _find_getch():
    try:
        import termios
    except ImportError:
        # Non-POSIX. Return msvcrt's (Windows') getch.
        import msvcrt
        return msvcrt.getch

    # POSIX system. Create and return a getch that manipulates the tty.
    import sys, tty
    def _getch():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

    return _getch

getch = _find_getch()

最简单的跨平台解决方案是sshkeyboard。使用pip安装sshkeyboard,

然后编写如下脚本:

from sshkeyboard import listen_keyboard

def press(key):
    print(f"'{key}' pressed")

def release(key):
    print(f"'{key}' released")

listen_keyboard(
    on_press=press,
    on_release=release,
)

它会打印:

'a' pressed
'a' released

当按下A键。默认按ESC键结束监听。

它需要比curses, tkinter和getch更少的编码。