Python从用户读取单个字符

Eva*_*ark 242 python input

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

teh*_*van 176

这是一个指向如何在Windows,Linux和OSX中读取单个字符的网站的链接:http://code.activestate.com/recipes/134892/

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()
Run Code Online (Sandbox Code Playgroud)

  • 代码看起来很短,你可以只包括它,但+1代表如此快速地找到一个好的(跨平台)答案. (17认同)
  • @Seismoid:要求宽恕通常被认为更好,请参阅http://stackoverflow.com/questions/12265451/ask-forgiveness-not-permission-explain (9认同)
  • 我不喜欢如何使用`ImportError`异常,就像某种if语句一样; 为什么不调用platform.system()来检查操作系统? (5认同)
  • 它能很好地处理非拉丁语(例如西里尔字母)的字母吗?我遇到了问题而无法弄明白,如果这是我的错误,或不是. (4认同)
  • 在OS X上不起作用:"old_settings = termios.tcgetattr(fd)""termios.error:(25,'不恰当的设备ioctl')" (3认同)
  • 为什么这些类会覆盖`__call__`而不是普通方法? (2认同)
  • 这些只是函数的伪类的使用是可怕的恕我直言。为什么不为每个模块组提供一个简单函数,并为一个函数确定并返回要使用的函数呢? (2认同)
  • @martineau Imports 创建单个模块实例(永远不会重新创建),然后将模块对象绑定到当前作用域中的名称。该名称不必要地本地化为“__call__”方法。除非有充分的理由,否则导入*应该*是模块级别的。“它们在 __init__() 中完成的原因是,如果它们不存在,则会导致 ImportError”,这最好在模块范围内的 `try`/` except` 块中处理;您可以在模块级别捕获异常。这样做来处理丢失的模块是 Python 中众所周知的模式。这是不必要的间接。 (2认同)

Yuv*_*dam 75

sys.stdin.read(1)
Run Code Online (Sandbox Code Playgroud)

基本上会从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()
Run Code Online (Sandbox Code Playgroud)

(摘自 http://code.activestate.com/recipes/134892/)

  • 我觉得奇怪,sys.stdin.read(1)等待一个\n,大声笑.谢谢你的提交. (31认同)
  • @Evan,这是因为默认情况下python处于行缓冲模式 (4认同)
  • 请注意,此代码阻止您使用^ C或^ D! (4认同)
  • 一个字符还是一个字节?那不一样. (3认同)
  • @EvanFosmark:sys.stdin.read(1)不一定等待\n,它是终端程序决定何时向你的程序发送其他字符不会写它们直到它看到'\n' - 怎么会你可以按退格键并更正你输入的内容吗?(严肃的答案是 - 教python程序实现行控制,保留缓冲区,处理退格键,但这是一个不同的世界,你可能不想在"阅读一个角色"时买进,并且可以使你的线处理与您系统上的所有其他程序不同.) (2认同)
  • @Seismoid [EAFP](http://stackoverflow.com/questions/11360858/what-is-the-eafp-principle-in-python) (2认同)

Lou*_*uis 64

在两个答案中逐字引用的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()
Run Code Online (Sandbox Code Playgroud)


Sør*_*org 45

另外值得一试的是readchar库,它部分基于其他答案中提到的ActiveState配方.

安装:

pip install readchar
Run Code Online (Sandbox Code Playgroud)

用法:

import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))
Run Code Online (Sandbox Code Playgroud)

使用Python 2.7在Windows和Linux上测试.

在Windows上,只有映射到字母或ASCII控制代码键的支持(Backspace,Enter,Esc,Tab,Ctrl+ 字母).在GNU/Linux(取决于具体终端上,也许?),你也可以得到Insert,Delete,Pg Up,Pg Dn,Home,End和键...但随后,有分离的这些特殊键问题.F nEsc

警告:像在这里的大多数(?全部)答案,信号键,如Ctrl+ C,Ctrl+ DCtrl+ Z被抓获和返回(如'\x03','\x04''\x1a'分别); 你的程序很难中止.

  • 也适用于Linux上的Python 3.比getch好多了,因为readchar允许在等待键(通过线程或asyncio)时打印到stdout. (3认同)
  • 这是最好的答案。仅为此功能而向 VS C++ 库添加依赖项是疯狂的。 (2认同)

Tyl*_*ler 16

另一种方法:

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
Run Code Online (Sandbox Code Playgroud)

来自这篇博文.

  • @Marein 如果您希望它阻塞(等待输入),请删除`| os.O_NONBLOCK`。否则,你可以把它放在一个循环中(在循环中睡一会儿以防止旋转是个好主意)。 (2认同)

Yas*_*thi 9

您可以使用单击。它经过充分测试,可在 Linux、Mac 和 Windows 上运行。

import click

print('Continue? [yn] ')
c = click.getchar()   # Gets a single character


if c == 'y':
    print('We will go on')
elif c == 'n':
    print('Abort!')
else:
    print('Invalid input :(')
Run Code Online (Sandbox Code Playgroud)


kir*_*iri 8

此代码基于此处,如果按下Ctrl+ CCtrl+ ,将正确引发KeyboardInterrupt和EOFError 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()
Run Code Online (Sandbox Code Playgroud)


小智 8

Try using this: http://home.wlu.edu/~levys/software/kbhit.py It's non-blocking (that means that you can have a while loop and detect a key press without stopping it) and cross-platform.

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 != []
Run Code Online (Sandbox Code Playgroud)

An example to use this:

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()
Run Code Online (Sandbox Code Playgroud)

Or you could use the getch module from PyPi. But this would block the while loop


Phy*_*ida 8

这里的答案信息丰富,但是我还想要一种方法来异步获取按键按下并在单独的事件中触发按键按下,所有这些都以线程安全、跨平台的方式进行。PyGame 对我来说也太臃肿了。所以我做了以下(在 Python 2.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  /sf/answers/141584061/
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()
Run Code Online (Sandbox Code Playgroud)

这个想法是你可以简单地调用keyPress.getKey(),它将从键盘读取一个键,然后返回它。

如果你想要更多的东西,我做了一个KeyCapture对象。您可以通过类似的方式创建一个keys = keyPress.KeyCapture()

然后你可以做三件事:

addEvent(functionName)接受任何接受一个参数的函数。然后每次按下一个键时,该函数将在输入时使用该键的字符串被调用。它们在单独的线程中运行,因此您可以阻止它们中的所有内容,并且不会弄乱 KeyCapturer 的功能,也不会延迟其他事件。

get()以与以前相同的阻塞方式返回一个键。现在这里需要它,因为现在正在通过KeyCapture对象捕获键,因此keyPress.getKey()会与该行为发生冲突,并且它们都会错过一些键,因为一次只能捕获一个键。另外,假设用户按“a”,然后按“b”,您调用get(),用户按“c”。该get()调用将立即返回 'a',然后如果您再次调用它,它将返回 'b',然后是 'c'。如果您再次调用它,它将阻塞,直到按下另一个键。这可以确保您不会错过任何键,如果需要,可以采用阻塞方式。所以这样keyPress.getKey()和以前有点不一样

如果你想要getKey()back的行为,get(lossy=True)就像get(),除了它只返回调用按下的键get()。所以在上面的例子中,get()会阻塞直到用户按下“c”,然后如果你再次调用它,它将阻塞直到按下另一个键。

getAsync()有点不同。它被设计用于进行大量处理,然后偶尔返回并检查按下了哪些键。因此getAsync()返回自上次调用以来按下的所有键的列表,按getAsync()从最旧的键到最近按下的键的顺序。它也不会阻塞,这意味着如果自上次调用 以来没有按下任何键getAsync()[]则将返回一个空 值。

要真正开始捕获键,您需要使用上面创建keys.startCapture()keys对象进行调用。startCapture是非阻塞的,只需启动一个只记录按键操作的线程,然后启动另一个线程来处理这些按键操作。有两个线程可以确保记录按键的线程不会遗漏任何键。

如果你想停止捕获密钥,你可以调用keys.stopCapture()它会停止捕获密钥。但是,由于捕获键是一种阻塞操作,因此捕获键的线程可能会在调用 之后再捕获一个键stopCapture()

为了防止这种情况,您可以将一个可选参数传递startCapture(functionName, args)给一个函数,该函数只执行诸如检查键是否等于 'c' 然后退出的操作。重要的是这个函数之前做的很少,例如,这里的睡眠会导致我们错过键。

但是,如果stopCapture()在此函数中调用,则键捕获将立即停止,不再尝试捕获,并且所有get()调用将立即返回,如果尚未按下任何键,则返回 None。

此外,由于get()getAsync()存储所有先前按下的键(直到您检索它们),您可以调用clearGetList()clearAsyncList()忘记先前按下的键。

请注意,get(),getAsync()和 事件是独立的,因此如果按下某个键:

  1. 一个get()正在等待的调用,有损打开,将返回该密钥。其他等待调用(如果有)将继续等待。
  2. 该键将存储在获取键的队列中,以便get()有损关闭将返回get()尚未返回的最旧键。
  3. 所有事件都将使用该键作为其输入触发
  4. 该键将存储在getAsync()键列表中,该列表将在下一次调用时返回并设置为空列表getAsync()

如果这一切都太多了,这里是一个示例用例:

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"
Run Code Online (Sandbox Code Playgroud)

从我所做的简单测试来看,它对我来说效果很好,但如果我遗漏了什么,我也会很乐意接受其他人的反馈。

也在这里发布了这个。


Mat*_*ber 6

(当前)排名靠前的答案(使用ActiveState代码)过于复杂.当一个单纯的功能足够时,我没有看到使用类的理由.下面是两个实现相同但具有更多可读代码的实现.

这两个实现:

  1. 在Python 2或Python 3中工作得很好
  2. 适用于Windows,OSX和Linux
  3. 只读一个字节(即他们不等待换行)
  4. 不依赖于任何外部库
  5. 是自包含的(函数定义之外没有代码)

版本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
Run Code Online (Sandbox Code Playgroud)

版本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()
Run Code Online (Sandbox Code Playgroud)

练习上述任一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))
Run Code Online (Sandbox Code Playgroud)

  • 在同时等待键(多线程)的同时打印消息时,我遇到了tty.setraw()的问题。长话短说,我发现使用tty.setcbreak()可以让您获得单个字符而不会破坏所有其他普通的东西。此[answer]中的长话[http://stackoverflow.com/questions/12231794/python-in-raw-mode-stdin-print-adds-spaces/37358649#37358649) (2认同)

小智 5

这可能是上下文管理器的用例。撇开 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()
Run Code Online (Sandbox Code Playgroud)


ibi*_*bic 5

ActiveState 的配方似乎包含一个“posix”系统的小错误,可以防止Ctrl-C中断(我使用的是 Mac)。如果我将以下代码放入我的脚本中:

while(True):
    print(getch())
Run Code Online (Sandbox Code Playgroud)

我永远无法用 终止脚本Ctrl-C,我必须杀死我的终端才能逃脱。

我相信下面这行就是原因,而且也太残酷了:

tty.setraw(sys.stdin.fileno())
Run Code Online (Sandbox Code Playgroud)

除此之外,packagetty并不是真正需要的,termios足以处理它。

下面是对我有用的改进代码(将中断),其中包含在您键入时回显字符的Ctrl-C额外函数:getche

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
                    # /sf/answers/3357401071/
                    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)
Run Code Online (Sandbox Code Playgroud)

参考:


qel*_*qel 5

如果我正在做一些复杂的事情,我会使用诅咒来读取密钥。但是很多时候我只想要一个简单的 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
Run Code Online (Sandbox Code Playgroud)


Mar*_*äki 5

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

\n

我知道我正在寻找 \xe2\x98\x9d\xef\xb8\x8f。您从 google 来到这里,想要一些不需要 pip install 这个那个就能工作的东西?我相当确定这个解决方案将继续有效很长一段时间。

\n

使用示例

\n
>>> getch_but_it_actually_works() # just normal key like a\n\'a\'\n\n>>> getch_but_it_actually_works() # a but its shift or capslock\n\'A\'\n\n>>> getch_but_it_actually_works() # just bare enter\n\'\\r\'\n\n>>> getch_but_it_actually_works() # literal ESC key\n\'\\x1b\'\n\n>>> getch_but_it_actually_works() # one of the arrow keys on linux\n\'\\x1b[A\'\n\n>>> getch_but_it_actually_works() # one of the arrow keys on windows\n\'\xc3\xa0K\'\n\n>>> getch_but_it_actually_works() # some really obscure key-combo. still works.\n\'\\x1b[19;6~\'\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

滚动查看最后的更详细答案,并带有合理的缩进和评论。这是方便复制粘贴的最大密度预览。只需调用getch_but_it_actually_works()

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

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

\n

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

\n

这很可能在 Linux 和 Windows 上都可以工作很长时间。没有外部依赖项,只有内置项。

\n

它还会处理边缘情况,例如按箭头键或像 <ctrl + shift + f12> 这样晦涩的东西,这将在 Linux 上产生长的 ANSI 转义序列,在 Windows 上产生其他东西。它将捕获诸如 <ctrl+x> 或 <ctrl+z> 或 tab 或 F1-12 作为单个输入

\n

这些年来,我已经几十次回到这篇文章了,所以现在是我回馈两美分和利息的时候了。下面是完整注释的代码。

\n

该示例有点长,但您可以跳过大部分内容。相关部分位于最后,您可以复制粘贴整个内容。

\n
\nimport os\n\ndef _read_one_wide_char_win():\n    """Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""\n    return msvcrt.getwch()\n\ndef _char_can_be_escape_win(char):\n    """Return true if char could start a multipart key code (e.g.: arrows)"""\n    return True if char in ("\\x00", "\xc3\xa0") else False # \\x00 is null character\n\ndef _dump_keyboard_buff_win():\n    """If piece of multipart keycode in buffer, return it. Else return None"""\n    try:                       # msvcrt.kbhit wont work with msvcrt.getwch\n        msvcrt.ungetwch("a")   # check buffer status by ungetching wchr\n    except OSError:            # ungetch fails > something in buffer so >\n        return msvcrt.getwch() # return the buffer note: win multipart keys\n    else:                      # are always 2 parts. if ungetwch does not fail\n        _ = msvcrt.getwch()    # clean up and return empty string\n        return ""\n\ndef _read_one_wide_char_nix():\n    """Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""\n    old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings\n    tty.setraw(sys.stdin.fileno()) # set raw mode to catch raw key w/o enter\n    wchar = sys.stdin.read(1)\n    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)\n    return wchar\n\ndef _char_can_be_escape_nix(char):\n    """Return true if char could start a multipart key code (e.g.: arrows)"""\n    return True if char == "\\x1b" else False # "\\x1b" is literal esc-key\n\ndef _dump_keyboard_buff_nix():\n    """If parts of multipart keycode in buffer, return them. Otherwise None"""\n    old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings\n    tty.setraw(sys.stdin.fileno()) # raw to read single key w/o enter\n    os.set_blocking(sys.stdin.fileno(), False) # dont block for empty buffer\n    buffer_dump = ""\n    while char := sys.stdin.read(1):\n        buffer_dump += char\n    os.set_blocking(sys.stdin.fileno(), True) # restore normal settings\n    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)\n    if buffer_dump:\n        return buffer_dump\n    else:\n        return ""\n\nif os.name == "nt":\n    import msvcrt\n    read_one_wdchar = _read_one_wide_char_win\n    char_can_escape = _char_can_be_escape_win\n    dump_key_buffer = _dump_keyboard_buff_win\nif os.name == "posix":\n    import termios\n    import tty\n    import sys\n    read_one_wdchar = _read_one_wide_char_nix\n    char_can_escape = _char_can_be_escape_nix\n    dump_key_buffer = _dump_keyboard_buff_nix\n\n\ndef getch_but_it_actually_works():\n    """Returns a printable character or a keycode corresponding to special key\n    like arrow or insert. Compatible with windows and linux, no external libs\n    except for builtins. Uses different builtins for windows and linux.\n\n    This function is more accurately called:\n    "get_wide_character_or_keycode_if_the_key_was_nonprintable()"\n\n    e.g.:\n        * returns "e" if e was pressed\n        * returns "E" if shift or capslock was on\n        * returns "x1b[19;6~\'" for ctrl + shift + F8 on unix\n\n    You can use string.isprintable() if you need to sometimes print the output\n    and sometimes use it for menu control and such. Printing raw ansi escape\n    codes can cause your terminal to do things like move cursor three rows up.\n\n    Enter will return "\\ r" on all platforms (without the space seen here)\n    as the enter key will produce carriage return, but windows and linux\n    interpret it differently in different contexts on higher level\n    """\n    wchar = read_one_wdchar()    # get first char from key press or key combo\n    if char_can_escape(wchar):   # if char is escapecode, more may be waiting\n        dump = dump_key_buffer() # dump buffer to check if more were waiting.\n        return wchar + dump      # return escape+buffer. buff could be just ""\n    else:                        # if buffer was empty then we return a single\n        return wchar             # key like "e" or "\\x1b" for the ESC button\n
Run Code Online (Sandbox Code Playgroud)\n