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


当前回答

TL;DR:这是你的无依赖跨平台最大密度复制面糊

我知道我一直在找☝️。你从谷歌来到这里,想要一些不需要pip安装这个和那个就能工作的东西?我相当肯定这个解决方案将继续工作很长一段时间。

示例使用

>>> getch_but_it_actually_works() # just normal key like a
'a'

>>> getch_but_it_actually_works() # a but its shift or capslock
'A'

>>> getch_but_it_actually_works() # just bare enter
'\r'

>>> getch_but_it_actually_works() # literal ESC key
'\x1b'

>>> getch_but_it_actually_works() # one of the arrow keys on linux
'\x1b[A'

>>> getch_but_it_actually_works() # one of the arrow keys on windows
'àK'

>>> getch_but_it_actually_works() # some really obscure key-combo. still works.
'\x1b[19;6~'

跨平台解决方案,无外部依赖

滚动到更详细的答案在结束理智的缩进和评论。这是最大密度预览,便于复制粘贴。只需调用getch_but_it_actually_works()

import os
def _read_one_wide_char_win(): return msvcrt.getwch()
def _char_can_be_escape_win(char): return True if char in ("\x00", "à") else False
def _dump_keyboard_buff_win():
    try: msvcrt.ungetwch("a")
    except OSError: return msvcrt.getwch()
    else: _ = msvcrt.getwch(); return ""
def _read_one_wide_char_nix():
    old_settings = termios.tcgetattr(sys.stdin.fileno()); tty.setraw(sys.stdin.fileno())
    wchar = sys.stdin.read(1)
    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings); return wchar
def _char_can_be_escape_nix(char): return True if char == "\x1b" else False
def _dump_keyboard_buff_nix():
    old_settings = termios.tcgetattr(sys.stdin.fileno())
    tty.setraw(sys.stdin.fileno()); os.set_blocking(sys.stdin.fileno(), False)
    buffer_dump = ""
    while char := sys.stdin.read(1): buffer_dump += char
    os.set_blocking(sys.stdin.fileno(), True); termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
    if buffer_dump: return buffer_dump
    else: return ""
if os.name == "nt":
    import msvcrt
    read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_win, _char_can_be_escape_win, _dump_keyboard_buff_win
if os.name == "posix":
    import termios, tty, sys
    read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_nix, _char_can_be_escape_nix, _dump_keyboard_buff_nix
def getch_but_it_actually_works():
    wchar = read_one_wdchar()
    if char_can_escape(wchar): dump = dump_key_buffer(); return wchar + dump
    else: return wchar


答案很长,代码带有注释和合理的缩进

这里是所有评论的长答案。仍然没有依赖关系。

这很可能在linux和windows上工作很长一段时间。没有外部依赖,只有内置。

它还将处理边缘情况,如敲击方向键或一些模糊的东西,如<ctrl + shift + f12>,这将在linux和windows上产生很长的ANSI转义序列。它将捕获诸如<ctrl+x>或<ctrl+z>或tab或F1-12作为单个输入

这些年来,我已经回到这个帖子上几十次了,所以现在是时候把两分钱和利息还给我了。下面是完整的注释代码。

这个例子有点长,但您可以跳过阅读大部分内容。相关的位在最后,你可以复制粘贴整个东西。


import os

def _read_one_wide_char_win():
    """Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
    return msvcrt.getwch()

def _char_can_be_escape_win(char):
    """Return true if char could start a multipart key code (e.g.: arrows)"""
    return True if char in ("\x00", "à") else False # \x00 is null character

def _dump_keyboard_buff_win():
    """If piece of multipart keycode in buffer, return it. Else return None"""
    try:                       # msvcrt.kbhit wont work with msvcrt.getwch
        msvcrt.ungetwch("a")   # check buffer status by ungetching wchr
    except OSError:            # ungetch fails > something in buffer so >
        return msvcrt.getwch() # return the buffer note: win multipart keys
    else:                      # are always 2 parts. if ungetwch does not fail
        _ = msvcrt.getwch()    # clean up and return empty string
        return ""

def _read_one_wide_char_nix():
    """Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
    old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
    tty.setraw(sys.stdin.fileno()) # set raw mode to catch raw key w/o enter
    wchar = sys.stdin.read(1)
    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
    return wchar

def _char_can_be_escape_nix(char):
    """Return true if char could start a multipart key code (e.g.: arrows)"""
    return True if char == "\x1b" else False # "\x1b" is literal esc-key

def _dump_keyboard_buff_nix():
    """If parts of multipart keycode in buffer, return them. Otherwise None"""
    old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
    tty.setraw(sys.stdin.fileno()) # raw to read single key w/o enter
    os.set_blocking(sys.stdin.fileno(), False) # dont block for empty buffer
    buffer_dump = ""
    while char := sys.stdin.read(1):
        buffer_dump += char
    os.set_blocking(sys.stdin.fileno(), True) # restore normal settings
    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
    if buffer_dump:
        return buffer_dump
    else:
        return ""

if os.name == "nt":
    import msvcrt
    read_one_wdchar = _read_one_wide_char_win
    char_can_escape = _char_can_be_escape_win
    dump_key_buffer = _dump_keyboard_buff_win
if os.name == "posix":
    import termios
    import tty
    import sys
    read_one_wdchar = _read_one_wide_char_nix
    char_can_escape = _char_can_be_escape_nix
    dump_key_buffer = _dump_keyboard_buff_nix


def getch_but_it_actually_works():
    """Returns a printable character or a keycode corresponding to special key
    like arrow or insert. Compatible with windows and linux, no external libs
    except for builtins. Uses different builtins for windows and linux.

    This function is more accurately called:
    "get_wide_character_or_keycode_if_the_key_was_nonprintable()"

    e.g.:
        * returns "e" if e was pressed
        * returns "E" if shift or capslock was on
        * returns "x1b[19;6~'" for ctrl + shift + F8 on unix

    You can use string.isprintable() if you need to sometimes print the output
    and sometimes use it for menu control and such. Printing raw ansi escape
    codes can cause your terminal to do things like move cursor three rows up.

    Enter will return "\ r" on all platforms (without the space seen here)
    as the enter key will produce carriage return, but windows and linux
    interpret it differently in different contexts on higher level
    """
    wchar = read_one_wdchar()    # get first char from key press or key combo
    if char_can_escape(wchar):   # if char is escapecode, more may be waiting
        dump = dump_key_buffer() # dump buffer to check if more were waiting.
        return wchar + dump      # return escape+buffer. buff could be just ""
    else:                        # if buffer was empty then we return a single
        return wchar             # key like "e" or "\x1b" for the ESC button

其他回答

如果您想只注册一个键,即使用户按了多次或长时间按该键也要按。 为了避免获得多个按下的输入,使用while循环并传递它。

import keyboard

while(True):
  if(keyboard.is_pressed('w')):
      s+=1
      while(keyboard.is_pressed('w')):
        pass
  if(keyboard.is_pressed('s')):
      s-=1
      while(keyboard.is_pressed('s')):
        pass
  print(s)

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

input()

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

这可能是上下文管理器的一个用例。不考虑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()

内置的raw_input应该会有所帮助。

for i in range(3):
    print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")

接受的答案对我来说并不是那么好(我按住一个键,什么都不会发生,然后我按下另一个键,它就会工作)。

在学习了curses模块之后,这似乎是正确的方法。现在它可以通过Windows -游标(通过pip可用)用于Windows,因此您可以以与平台无关的方式进行编程。下面这个例子是受YouTube上这个教程的启发:

import curses                                                                                                                                       
def getkey(stdscr):
    curses.curs_set(0)
    while True:
        key = stdscr.getch()
        if key != -1:
            break
    return key

if __name__ == "__main__":
    print(curses.wrapper(getkey))

以.py扩展名保存,或在交互模式下运行curses.wrapper(getkey)。