是否有办法从用户输入中读取单个字符?例如,他们在终端上按下一个键,它就会返回(有点像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

其他回答

内置的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.")

试试这个:http://home.wlu.edu/~levys/software/kbhit.py 它是非阻塞的(这意味着您可以有一个while循环并检测按键而无需停止它)和跨平台的。

import os

# Windows
if os.name == 'nt':
    import msvcrt

# Posix (Linux, OS X)
else:
    import sys
    import termios
    import atexit
    from select import select


class KBHit:

    def __init__(self):
        '''Creates a KBHit object that you can call to do various keyboard things.'''

        if os.name == 'nt':
            pass

        else:

            # Save the terminal settings
            self.fd = sys.stdin.fileno()
            self.new_term = termios.tcgetattr(self.fd)
            self.old_term = termios.tcgetattr(self.fd)

            # New terminal setting unbuffered
            self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)

            # Support normal-terminal reset at exit
            atexit.register(self.set_normal_term)


    def set_normal_term(self):
        ''' Resets to normal terminal.  On Windows this is a no-op.
        '''

        if os.name == 'nt':
            pass

        else:
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)


    def getch(self):
        ''' Returns a keyboard character after kbhit() has been called.
            Should not be called in the same program as getarrow().
        '''

        s = ''

        if os.name == 'nt':
            return msvcrt.getch().decode('utf-8')

        else:
            return sys.stdin.read(1)


    def getarrow(self):
        ''' Returns an arrow-key code after kbhit() has been called. Codes are
        0 : up
        1 : right
        2 : down
        3 : left
        Should not be called in the same program as getch().
        '''

        if os.name == 'nt':
            msvcrt.getch() # skip 0xE0
            c = msvcrt.getch()
            vals = [72, 77, 80, 75]

        else:
            c = sys.stdin.read(3)[2]
            vals = [65, 67, 66, 68]

        return vals.index(ord(c.decode('utf-8')))


    def kbhit(self):
        ''' Returns True if keyboard character was hit, False otherwise.
        '''
        if os.name == 'nt':
            return msvcrt.kbhit()

        else:
            dr,dw,de = select([sys.stdin], [], [], 0)
            return dr != []

使用这个的例子:

import kbhit

kb = kbhit.KBHit()

while(True): 
    print("Key not pressed") #Do something
    if kb.kbhit(): #If a key is pressed:
        k_in = kb.getch() #Detect what key was pressed
        print("You pressed ", k_in, "!") #Do something
kb.set_normal_term()

或者您可以使用PyPi中的getch模块。但是这会阻塞while循环

如果我在做一些复杂的事情,我会使用诅咒来读取键。但很多时候,我只是想要一个简单的Python 3脚本,使用标准库,可以读取方向键,所以我这样做:

import sys, termios, tty

key_Enter = 13
key_Esc = 27
key_Up = '\033[A'
key_Dn = '\033[B'
key_Rt = '\033[C'
key_Lt = '\033[D'

fdInput = sys.stdin.fileno()
termAttr = termios.tcgetattr(0)

def getch():
    tty.setraw(fdInput)
    ch = sys.stdin.buffer.raw.read(4).decode(sys.stdin.encoding)
    if len(ch) == 1:
        if ord(ch) < 32 or ord(ch) > 126:
            ch = ord(ch)
    elif ord(ch[0]) == 27:
        ch = '\033' + ch[1:]
    termios.tcsetattr(fdInput, termios.TCSADRAIN, termAttr)
    return ch

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

在学习了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)。

另一种方法:

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

摘自这篇博文。