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


当前回答

最简单的跨平台解决方案是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更少的编码。

其他回答

(目前)排名第一的答案(带有ActiveState代码)过于复杂。当仅仅一个函数就足够了时,我不认为有理由使用类。下面是两个实现,它们实现了相同的功能,但代码可读性更强。

这两个实现:

可以在python2或python3中正常工作 可以在Windows, OSX和Linux上工作 只读取一个字节(即,它们不等待换行符) 不要依赖任何外部库 是自包含的(没有函数定义之外的代码)

版本1:易读且简单

def getChar():
    try:
        # for Windows-based systems
        import msvcrt # If successful, we are on Windows
        return msvcrt.getch()

    except ImportError:
        # for POSIX-based systems (with termios & tty support)
        import tty, sys, termios  # raises ImportError if unsupported

        fd = sys.stdin.fileno()
        oldSettings = termios.tcgetattr(fd)

        try:
            tty.setcbreak(fd)
            answer = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)

        return answer

版本2:避免重复导入和异常处理:

[编辑]我错过了ActiveState代码的一个优点。如果您计划多次读取字符,该代码可以避免在类unix系统上重复Windows导入和ImportError异常处理的成本(可以忽略不计)。虽然你可能应该更关心代码的可读性,而不是可以忽略的优化,这里有一个替代方案(它类似于Louis的答案,但getChar()是自包含的),它的功能与ActiveState代码相同,更可读:

def getChar():
    # figure out which function to use once, and store it in _func
    if "_func" not in getChar.__dict__:
        try:
            # for Windows-based systems
            import msvcrt # If successful, we are on Windows
            getChar._func=msvcrt.getch

        except ImportError:
            # for POSIX-based systems (with termios & tty support)
            import tty, sys, termios # raises ImportError if unsupported

            def _ttyRead():
                fd = sys.stdin.fileno()
                oldSettings = termios.tcgetattr(fd)

                try:
                    tty.setcbreak(fd)
                    answer = sys.stdin.read(1)
                finally:
                    termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)

                return answer

            getChar._func=_ttyRead

    return getChar._func()

使用上述getChar()版本的示例代码:

from __future__ import print_function # put at top of file if using Python 2

# Example of a prompt for one character of input
promptStr   = "Please give me a character:"
responseStr = "Thank you for giving me a '{}'."
print(promptStr, end="\n> ")
answer = getChar()
print("\n")
print(responseStr.format(answer))

另一种方法:

import os
import sys    
import termios
import fcntl

def getch():
  fd = sys.stdin.fileno()

  oldterm = termios.tcgetattr(fd)
  newattr = termios.tcgetattr(fd)
  newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
  termios.tcsetattr(fd, termios.TCSANOW, newattr)

  oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
  fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

  try:        
    while 1:            
      try:
        c = sys.stdin.read(1)
        break
      except IOError: pass
  finally:
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
  return c

摘自这篇博文。

这段代码,基于这里,将正确地引发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()

值得一试的是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'),这可能是也可能不是可取的。

两个答案中逐字引用的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()