Python 如何从用户处读取单个字符?

Python 如何从用户处读取单个字符?,python,input,Python,Input,有没有办法从用户输入中读取一个字符?例如,他们在终端上按一个键,然后返回(有点像getch())。我知道Windows中有一个函数,但我想要一个跨平台的函数。这里有一个指向ActiveState Recipes站点的链接,上面说如何在Windows、Linux和OSX中读取单个字符: import os # Windows if os.name == 'nt': import msvcrt # Posix (Linux, OS X) else: import sys

有没有办法从用户输入中读取一个字符?例如,他们在终端上按一个键,然后返回(有点像
getch()
)。我知道Windows中有一个函数,但我想要一个跨平台的函数。

这里有一个指向ActiveState Recipes站点的链接,上面说如何在Windows、Linux和OSX中读取单个字符:

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 != []

基本上从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()
(摘自)

另一种方法:

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
从。

如果按下Ctrl+C或Ctrl+D,此代码基于off,将正确启动键盘中断和EOFEROR

应该在Windows和Linux上工作。OSX版本可从原始来源获得

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()
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()

这是非阻塞的,读取密钥并将其存储在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
同样值得一试的是这个库,它部分基于其他答案中提到的ActiveState配方

安装:

pip install readchar
用法:

import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))
使用Python 2.7在Windows和Linux上测试

在Windows上,仅支持映射到字母或ASCII控制代码的键(Backspace、Enter、Esc、Tab、Ctrl+letter)。在GNU/Linux上(可能取决于具体的终端),您还可以获得Insert、Delete、Pg Up、Pg Dn、Home、End和FN键。。。但是,将这些特殊键与Esc分离存在问题


警告:与这里的大多数(所有?)答案一样,Ctrl+C、Ctrl+D和Ctrl+Z等信号键被捕获并返回(分别为
'\x03'
'\x04'
'\x1a'
);您的程序可能很难中止。

这可能是上下文管理器的一个用例。撇开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()
#/usr/bin/env蟒蛇3
#文件:“readchar.py”
"""
一种获取单个输入字符的方法的实现
无需等待用户点击。
(操作系统是Linux,Ubuntu 14.04)
"""
导入tty、sys、termios
类ReadChar():
定义输入(自我):
self.fd=sys.stdin.fileno()
self.old_settings=termios.tcgetattr(self.fd)
tty.setraw(sys.stdin.fileno())
返回系统标准读数(1)
定义退出(自身、类型、值、回溯):
termios.tcsetattr(self.fd、termios.TCSADRAIN、self.old_设置)
def test():
尽管如此:
使用ReadChar()作为rc:
char=rc

如果ord(char)python中的
curses
包可以用来进入“raw”模式,只需几个语句即可从终端输入字符。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!')
用pygame试试这个:

import pygame
pygame.init()             // eliminate error, pygame.error: video system not initialized
keys = pygame.key.get_pressed()

if keys[pygame.K_SPACE]:
    d = "space key"

print "You pressed the", d, "."
尝试使用以下方法: 它是非阻塞的(这意味着你可以有一个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()
或者你也可以用这个。但是这会阻止while循环,答案是信息性的,但是我也想要一种异步按键的方法,并在单独的事件中触发按键,所有这些都是以线程安全的跨平台方式进行的。PyGame对我来说也太臃肿了。所以我做了以下内容(在Python2.7中,但我怀疑它很容易移植),我想在这里分享一下,以防它对其他人有用。我将其存储在一个名为keyPress.py的文件中

class _Getch:
    """Gets a single character from standard input.  Does not echo to the
screen. From http://code.activestate.com/recipes/134892/"""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            try:
                self.impl = _GetchMacCarbon()
            except(AttributeError, ImportError):
                self.impl = _GetchUnix()

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


class _GetchUnix:
    def __init__(self):
        import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac

    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()

class _GetchMacCarbon:
    """
    A function which returns the current ASCII key that is down;
    if no ASCII key is down, the null string is returned.  The
    page http://www.mactech.com/macintosh-c/chap02-1.html was
    very helpful in figuring out how to do this.
    """
    def __init__(self):
        import Carbon
        Carbon.Evt #see if it has this (in Unix, it doesn't)

    def __call__(self):
        import Carbon
        if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask
            return ''
        else:
            #
            # The event contains the following info:
            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            #
            # The message (msg) contains the ASCII char which is
            # extracted with the 0x000000FF charCodeMask; this
            # number is converted to an ASCII character with chr() and
            # returned
            #
            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            return chr(msg & 0x000000FF)

import threading
            
            
# From  https://stackoverflow.com/a/2022629/2924421
class Event(list):
    def __call__(self, *args, **kwargs):
        for f in self:
            f(*args, **kwargs)

    def __repr__(self):
        return "Event(%s)" % list.__repr__(self)            


def getKey():
    inkey = _Getch()
    import sys
    for i in xrange(sys.maxint):
        k=inkey()
        if k<>'':break
    return k

class KeyCallbackFunction():
    callbackParam = None
    actualFunction = None
    
    def __init__(self, actualFunction, callbackParam):
        self.actualFunction = actualFunction
        self.callbackParam = callbackParam

    def doCallback(self, inputKey):
        if not self.actualFunction is None:
            if self.callbackParam is None:
                callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,))
            else:
                callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,self.callbackParam))
            
            callbackFunctionThread.daemon = True
            callbackFunctionThread.start()
        
        

class KeyCapture():


    gotKeyLock = threading.Lock()
    gotKeys = []
    gotKeyEvent = threading.Event()

    keyBlockingSetKeyLock = threading.Lock()

    addingEventsLock = threading.Lock()
    keyReceiveEvents = Event()


    keysGotLock = threading.Lock()
    keysGot = []

    keyBlockingKeyLockLossy = threading.Lock()
    keyBlockingKeyLossy = None
    keyBlockingEventLossy = threading.Event()
    
    keysBlockingGotLock = threading.Lock()
    keysBlockingGot = []
    keyBlockingGotEvent = threading.Event()
    

    
    wantToStopLock = threading.Lock()
    wantToStop = False
    
    stoppedLock = threading.Lock()
    stopped = True
    
    isRunningEvent = False
    
    getKeyThread = None
    
    keyFunction = None
    keyArgs = None
    
    # Begin capturing keys. A seperate thread is launched that
    # captures key presses, and then these can be received via get,
    # getAsync, and adding an event via addEvent. Note that this
    # will prevent the system to accept keys as normal (say, if
    # you are in a python shell) because it overrides that key
    # capturing behavior.
    
    # If you start capture when it's already been started, a
    # InterruptedError("Keys are still being captured")
    # will be thrown
    
    # Note that get(), getAsync() and events are independent, so if a key is pressed:
    #
    # 1: Any calls to get() that are waiting, with lossy on, will return
    #    that key
    # 2: It will be stored in the queue of get keys, so that get() with lossy
    #    off will return the oldest key pressed not returned by get() yet.
    # 3: All events will be fired with that key as their input
    # 4: It will be stored in the list of getAsync() keys, where that list
    #    will be returned and set to empty list on the next call to getAsync().
    # get() call with it, aand add it to the getAsync() list.
    def startCapture(self, keyFunction=None, args=None):
        # Make sure we aren't already capturing keys
        self.stoppedLock.acquire()
        if not self.stopped:
            self.stoppedLock.release()
            raise InterruptedError("Keys are still being captured")
            return
        self.stopped = False
        self.stoppedLock.release()
        
        # If we have captured before, we need to allow the get() calls to actually
        # wait for key presses now by clearing the event
        if self.keyBlockingEventLossy.is_set():
            self.keyBlockingEventLossy.clear()
            
        # Have one function that we call every time a key is captured, intended for stopping capture
        # as desired
        self.keyFunction = keyFunction
        self.keyArgs = args
        
        # Begin capturing keys (in a seperate thread)
        self.getKeyThread = threading.Thread(target=self._threadProcessKeyPresses)
        self.getKeyThread.daemon = True
        self.getKeyThread.start()
        
        # Process key captures (in a seperate thread)
        self.getKeyThread = threading.Thread(target=self._threadStoreKeyPresses)
        self.getKeyThread.daemon = True
        self.getKeyThread.start()
    
    
    def capturing(self):
        self.stoppedLock.acquire()
        isCapturing = not self.stopped
        self.stoppedLock.release()
        return isCapturing
    # Stops the thread that is capturing keys on the first opporunity
    # has to do so. It usually can't stop immediately because getting a key
    # is a blocking process, so this will probably stop capturing after the
    # next key is pressed.
    #
    # However, Sometimes if you call stopCapture it will stop before starting capturing the
    # next key, due to multithreading race conditions. So if you want to stop capturing
    # reliably, call stopCapture in a function added via addEvent. Then you are
    # guaranteed that capturing will stop immediately after the rest of the callback
    # functions are called (before starting to capture the next key).
    def stopCapture(self):
        self.wantToStopLock.acquire()
        self.wantToStop = True 
        self.wantToStopLock.release()

    # Takes in a function that will be called every time a key is pressed (with that
    # key passed in as the first paramater in that function)
    def addEvent(self, keyPressEventFunction, args=None):   
        self.addingEventsLock.acquire()
        callbackHolder = KeyCallbackFunction(keyPressEventFunction, args)
        self.keyReceiveEvents.append(callbackHolder.doCallback)
        self.addingEventsLock.release()
    def clearEvents(self):
        self.addingEventsLock.acquire()
        self.keyReceiveEvents = Event()
        self.addingEventsLock.release()
    # Gets a key captured by this KeyCapture, blocking until a key is pressed.
    # There is an optional lossy paramater:
    # If True all keys before this call are ignored, and the next pressed key
    #   will be returned.
    # If False this will return the oldest key captured that hasn't
    #   been returned by get yet. False is the default.
    def get(self, lossy=False):
        if lossy:
            # Wait for the next key to be pressed
            self.keyBlockingEventLossy.wait()
            self.keyBlockingKeyLockLossy.acquire()
            keyReceived = self.keyBlockingKeyLossy
            self.keyBlockingKeyLockLossy.release()
            return keyReceived
        else:
            while True:
                # Wait until a key is pressed
                self.keyBlockingGotEvent.wait()
                
                # Get the key pressed
                readKey = None
                self.keysBlockingGotLock.acquire()
                # Get a key if it exists
                if len(self.keysBlockingGot) != 0:
                    readKey = self.keysBlockingGot.pop(0)
                # If we got the last one, tell us to wait
                if len(self.keysBlockingGot) == 0:
                    self.keyBlockingGotEvent.clear()
                self.keysBlockingGotLock.release()
                
                # Process the key (if it actually exists)
                if not readKey is None:
                    return readKey
                
                # Exit if we are stopping
                self.wantToStopLock.acquire()
                if self.wantToStop:
                    self.wantToStopLock.release()
                    return None
                self.wantToStopLock.release()
            
            
            
    
    def clearGetList(self):
        self.keysBlockingGotLock.acquire()
        self.keysBlockingGot = []
        self.keysBlockingGotLock.release()
    
    # Gets a list of all keys pressed since the last call to getAsync, in order
    # from first pressed, second pressed, .., most recent pressed
    def getAsync(self):
        self.keysGotLock.acquire();
        keysPressedList = list(self.keysGot)
        self.keysGot = []
        self.keysGotLock.release()
        return keysPressedList
    
    def clearAsyncList(self):
        self.keysGotLock.acquire();
        self.keysGot = []
        self.keysGotLock.release();

    def _processKey(self, readKey):
        # Append to list for GetKeyAsync
        self.keysGotLock.acquire()
        self.keysGot.append(readKey)
        self.keysGotLock.release()
        
        # Call lossy blocking key events
        self.keyBlockingKeyLockLossy.acquire()
        self.keyBlockingKeyLossy = readKey
        self.keyBlockingEventLossy.set()
        self.keyBlockingEventLossy.clear()
        self.keyBlockingKeyLockLossy.release()
        
        # Call non-lossy blocking key events
        self.keysBlockingGotLock.acquire()
        self.keysBlockingGot.append(readKey)
        if len(self.keysBlockingGot) == 1:
            self.keyBlockingGotEvent.set()
        self.keysBlockingGotLock.release()
        
        # Call events added by AddEvent
        self.addingEventsLock.acquire()
        self.keyReceiveEvents(readKey)
        self.addingEventsLock.release()

    def _threadProcessKeyPresses(self):
        while True:
            # Wait until a key is pressed
            self.gotKeyEvent.wait()
            
            # Get the key pressed
            readKey = None
            self.gotKeyLock.acquire()
            # Get a key if it exists
            if len(self.gotKeys) != 0:
                readKey = self.gotKeys.pop(0)
            # If we got the last one, tell us to wait
            if len(self.gotKeys) == 0:
                self.gotKeyEvent.clear()
            self.gotKeyLock.release()
            
            # Process the key (if it actually exists)
            if not readKey is None:
                self._processKey(readKey)
            
            # Exit if we are stopping
            self.wantToStopLock.acquire()
            if self.wantToStop:
                self.wantToStopLock.release()
                break
            self.wantToStopLock.release()
            
    def _threadStoreKeyPresses(self):
        while True:
            # Get a key
            readKey = getKey()
            
            # Run the potential shut down function
            if not self.keyFunction is None:
                self.keyFunction(readKey, self.keyArgs)
        
            # Add the key to the list of pressed keys
            self.gotKeyLock.acquire()
            self.gotKeys.append(readKey)
            if len(self.gotKeys) == 1:
                self.gotKeyEvent.set()
            self.gotKeyLock.release()
            
            # Exit if we are stopping
            self.wantToStopLock.acquire()
            if self.wantToStop:
                self.wantToStopLock.release()
                self.gotKeyEvent.set()
                break
            self.wantToStopLock.release()
    
        
        # If we have reached here we stopped capturing
        
        # All we need to do to clean up is ensure that
        # all the calls to .get() now return None.
        # To ensure no calls are stuck never returning,
        # we will leave the event set so any tasks waiting
        # for it immediately exit. This will be unset upon
        # starting key capturing again.
        
        self.stoppedLock.acquire()
        
        # We also need to set this to True so we can start up
        # capturing again.
        self.stopped = True
        self.stopped = True
        
        self.keyBlockingKeyLockLossy.acquire()
        self.keyBlockingKeyLossy = None
        self.keyBlockingEventLossy.set()
        self.keyBlockingKeyLockLossy.release()
        
        self.keysBlockingGotLock.acquire()
        self.keyBlockingGotEvent.set()
        self.keysBlockingGotLock.release()
        
        self.stoppedLock.release()
从我做的简单测试来看,它对我很有效,但是如果我遗漏了什么,我也会很乐意接受别人的反馈


我也发布了这篇文章。

内置的原始输入应该会有所帮助

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

我的python3解决方案不依赖于任何pip包

# precondition: import tty, sys
def query_yes_no(question, default=True):
    """
    Ask the user a yes/no question.
    Returns immediately upon reading one-char answer.
    Accepts multiple language characters for yes/no.
    """
    if not sys.stdin.isatty():
        return default
    if default:
        prompt = "[Y/n]?"
        other_answers = "n"
    else:
        prompt = "[y/N]?"
        other_answers = "yjosiá"

    print(question,prompt,flush= True,end=" ")
    oldttysettings = tty.tcgetattr(sys.stdin.fileno())
    try:
        tty.setraw(sys.stdin.fileno())
        return not sys.stdin.read(1).lower() in other_answers
    except:
        return default
    finally:
        tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)
        sys.stdout.write("\r\n")
        tty.tcdrain(sys.stdin.fileno())
(当前)排名靠前的答案(带有ActiveState代码)过于复杂。我看不出在仅仅一个函数就足够的情况下使用类的理由。下面是两个实现,它们实现了相同的功能,但代码可读性更强

这两种实现方式:

  • 在Python2或Python3中工作正常
  • 在Windows、OSX和Linux上工作
  • 只读取一个字节(即,它们不等待换行)
  • 不要依赖任何外部库
  • 是自包含的(函数定义之外没有代码)
  • 第1版:可读且简单

    def getChar():
    尝试:
    #适用于基于Windows的系统
    导入msvcrt#如果成功,我们将使用Windows
    返回msvcrt.getch()
    除恐怖外:
    #对于基于POSIX的系统(支持termios和tty)
    导入tty、sys、termios#如果不支持,则引发导入错误
    fd=sys.stdin.fileno()
    oldSettings=termios.tcgetattr(fd)
    尝试:
    tty.setcbreak(fd)
    答案=系统标准读数(1)
    最后:
    termios.tcsetattr(fd、termios.TCSADRAIN、oldSettings)
    回覆
    
    版本2:避免重复导入和异常处理:

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

    def getChar():
    #找出要使用一次的函数,并将其存储在_func中
    如果getChar中没有“\u func”。\u dict\u
    尝试:
    #适用于基于Windows的系统
    
    import keyPress
    import time
    import threading
    
    def KeyPressed(k, printLock):
        printLock.acquire()
        print "Event: " + k
        printLock.release()
        time.sleep(4)
        printLock.acquire()
        print "Event after delay: " + k
        printLock.release()
    
    def GetKeyBlocking(keys, printLock):    
        while keys.capturing():
            keyReceived = keys.get()
            time.sleep(1)
            printLock.acquire()
            if not keyReceived is None:
                print "Block " + keyReceived
            else:
                print "Block None"
            printLock.release()
    
    def GetKeyBlockingLossy(keys, printLock):   
        while keys.capturing():
            keyReceived = keys.get(lossy=True)
            time.sleep(1)
            printLock.acquire()
            if not keyReceived is None:
                print "Lossy: " + keyReceived
            else:
                print "Lossy: None"
            printLock.release()
    
    def CheckToClose(k, (keys, printLock)):
        printLock.acquire()
        print "Close: " + k
        printLock.release()
        if k == "c":
            keys.stopCapture()
            
    printLock = threading.Lock()
    
    print "Press a key:"
    print "You pressed: " + keyPress.getKey()
    print ""
    
    keys = keyPress.KeyCapture()
    
    keys.addEvent(KeyPressed, printLock)
    
    
    
    print "Starting capture"
                
    keys.startCapture(CheckToClose, (keys, printLock))
                
    getKeyBlockingThread = threading.Thread(target=GetKeyBlocking, args=(keys, printLock))
    getKeyBlockingThread.daemon = True
    getKeyBlockingThread.start()
    
                
    getKeyBlockingThreadLossy = threading.Thread(target=GetKeyBlockingLossy, args=(keys, printLock))
    getKeyBlockingThreadLossy.daemon = True
    getKeyBlockingThreadLossy.start()
    
    while keys.capturing():
        keysPressed = keys.getAsync()
        printLock.acquire()
        if keysPressed != []:
            print "Async: " + str(keysPressed)
        printLock.release()
        time.sleep(1)
    
    print "done capturing"
    
    for i in range(3):
        print ("So much work to do!")
    k = raw_input("Press any key to continue...")
    print ("Ok, back to work.")
    
    # precondition: import tty, sys
    def query_yes_no(question, default=True):
        """
        Ask the user a yes/no question.
        Returns immediately upon reading one-char answer.
        Accepts multiple language characters for yes/no.
        """
        if not sys.stdin.isatty():
            return default
        if default:
            prompt = "[Y/n]?"
            other_answers = "n"
        else:
            prompt = "[y/N]?"
            other_answers = "yjosiá"
    
        print(question,prompt,flush= True,end=" ")
        oldttysettings = tty.tcgetattr(sys.stdin.fileno())
        try:
            tty.setraw(sys.stdin.fileno())
            return not sys.stdin.read(1).lower() in other_answers
        except:
            return default
        finally:
            tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)
            sys.stdout.write("\r\n")
            tty.tcdrain(sys.stdin.fileno())
    
    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)
    
    import os
    
    if os.name == 'nt':
        import msvcrt
        def getch():
            return msvcrt.getch().decode()
    else:
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        def getch():
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(1)
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
            return ch
    
    if getch() == chr(ESC_ASCII_VALUE):
        print("ESC!")
    
    while(True):
        print(getch())
    
    tty.setraw(sys.stdin.fileno())
    
    if sys.platform == 'win32':
        import msvcrt
        getch = msvcrt.getch
        getche = msvcrt.getche
    else:
        import sys
        import termios
        def __gen_ch_getter(echo):
            def __fun():
                fd = sys.stdin.fileno()
                oldattr = termios.tcgetattr(fd)
                newattr = oldattr[:]
                try:
                    if echo:
                        # disable ctrl character printing, otherwise, backspace will be printed as "^?"
                        lflag = ~(termios.ICANON | termios.ECHOCTL)
                    else:
                        lflag = ~(termios.ICANON | termios.ECHO)
                    newattr[3] &= lflag
                    termios.tcsetattr(fd, termios.TCSADRAIN, newattr)
                    ch = sys.stdin.read(1)
                    if echo and ord(ch) == 127: # backspace
                        # emulate backspace erasing
                        # https://stackoverflow.com/a/47962872/404271
                        sys.stdout.write('\b \b')
                finally:
                    termios.tcsetattr(fd, termios.TCSADRAIN, oldattr)
                return ch
            return __fun
        getch = __gen_ch_getter(False)
        getche = __gen_ch_getter(True)
    
    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
    
    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))
    
    from tkinter import Tk, Frame
    
    
    def __set_key(e, root):
        """
        e - event with attribute 'char', the released key
        """
        global key_pressed
        if e.char:
            key_pressed = e.char
            root.destroy()
    
    
    def get_key(msg="Press any key ...", time_to_sleep=3):
        """
        msg - set to empty string if you don't want to print anything
        time_to_sleep - default 3 seconds
        """
        global key_pressed
        if msg:
            print(msg)
        key_pressed = None
        root = Tk()
        root.overrideredirect(True)
        frame = Frame(root, width=0, height=0)
        frame.bind("<KeyRelease>", lambda f: __set_key(f, root))
        frame.pack()
        root.focus_set()
        frame.focus_set()
        frame.focus_force()  # doesn't work in a while loop without it
        root.after(time_to_sleep * 1000, func=root.destroy)
        root.mainloop()
        root = None  # just in case
        return key_pressed
    
    
    def __main():
            c = None
            while not c:
                    c = get_key("Choose your weapon ... ", 2)
            print(c)
    
    if __name__ == "__main__":
        __main()
    
    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()