Python читает один символ от пользователя

Есть ли способ чтения одного символа с пользовательского ввода? Например, они нажимают один ключ на терминале и возвращаются (вроде как getch()). Я знаю, что для них есть функция в Windows, но мне нужно что-то кросс-платформенное.

Ответ 1

Здесь ссылка на сайт, в котором говорится, как вы можете читать один символ в 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()

Ответ 2

sys.stdin.read(1)

будет в основном читать 1 байт от STDIN.

Если вы должны использовать метод, который не ждет \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()

(взято из http://code.activestate.com/recipes/134892/)

Ответ 3

ActiveState recipe, указанный в двух ответах, является чрезмерно разработанным. Это можно свести к следующему:

def _find_getch():
    try:
        import termios
    except ImportError:
        # Non-POSIX. Return msvcrt (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()

Ответ 4

Также стоит попробовать библиотеку readchar, которая частично основана на рецепте ActiveState, упомянутом в других ответах.

Установка:

pip install readchar

Использование:

import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))

Протестировано в Windows и Linux с помощью Python 2.7.

В Windows поддерживаются только клавиши, которые сопоставляются буквам или кодам управления ASCII (Backspace, Enter, Esc, Tab, Ctrl + letter). На GNU/Linux (в зависимости от точного терминала, возможно?) Вы также получаете ключи Insert, Delete, Pg Up, Pg Dn, Home, End и F n... но затем, там выделяются эти специальные клавиши из Esc.

Предостережение. Как и в большинстве (все?) ответы здесь, сигнальные клавиши типа Ctrl + C, Ctrl + D и Ctrl + Z пойманы и возвращены (как '\x03', '\x04' и '\x1a' соответственно); вашей программе может быть сложно отказаться.

Ответ 5

Альтернативный метод:

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

От этого сообщения в блоге.

Ответ 6

Я думаю, что в этот момент он становится очень неуклюжим, а отладка на разных платформах - большой беспорядок.

Вам лучше использовать что-то вроде pyglet, pygame, cocos2d - если вы делаете что-то более сложное, чем это, и вам понадобятся визуальные, OR curses, если вы собираетесь работать с терминалом.

Curses является стандартным: http://docs.python.org/library/curses.html

Ответ 7

Этот код, основанный на здесь, будет правильно поднять KeyboardInterrupt и EOFError, если нажаты Ctrl + C или Ctrl + D.

Должно работать в Windows и Linux. Версия OS X доступна из исходного источника.

class _Getch:
    """Gets a single character from standard input.  Does not echo to the screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): 
        char = self.impl()
        if char == '\x03':
            raise KeyboardInterrupt
        elif char == '\x04':
            raise EOFError
        return char

class _GetchUnix:
    def __init__(self):
        import tty
        import sys

    def __call__(self):
        import sys
        import tty
        import termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()

Ответ 8

Ответ (в настоящее время) верхнего уровня (с кодом ActiveState) слишком сложный. Я не вижу причины использовать классы, когда достаточно простой функции. Ниже приведены две реализации, которые выполняют одно и то же, но с более читаемым кодом.

Обе эти реализации:

  • отлично работает в Python 2 или Python 3
  • работа с Windows, OSX и Linux.
  • читать только один байт (т.е. они не ждут новой строки)
  • не зависят от каких-либо внешних библиотек
  • являются автономными (без кода вне определения функции)

Версия 1: читаемая и простая

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

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

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

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

        return answer

Версия 2: избегайте повторных импорта и обработки исключений:

[EDIT] Я пропустил одно преимущество кода ActiveState. Если вы планируете читать символы несколько раз, этот код избегает (незначительной) стоимости повторения импорта Windows и обработки исключений ImportError для Unix-подобных систем. Хотя вы, вероятно, должны больше заботиться о читаемости кода, чем эта незначительная оптимизация, вот альтернатива (она похожа на ответ Луи, но getChar() является автономной), которая функционирует так же, как и код ActiveState, и более читаема:/p >

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

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

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

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

                return answer

            getChar._func=_ttyRead

    return getChar._func()

Пример кода, который использует обе версии getChar() выше:

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

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

Ответ 9

Это может быть использование для менеджера контекста. Оставляя в стороне скидки для ОС 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()

Ответ 10

Это НЕ БЛОКИРОВКА, считывает ключ и сохраняет его в 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

в вашей программе

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)

Ответ 11

Попробуйте использовать это: 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()

Или вы можете использовать модуль getch из PyPi. Но это блокирует цикл while

Ответ 12

Ответы здесь были информативными, однако я также хотел, чтобы асинхронно получать нажатия клавиш и отключать нажатия клавиш в отдельных событиях, все в поточно-безопасном, кросс-платформенном способе. 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  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 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()

Идея состоит в том, что вы можете просто вызвать keyPress.getKey(), который будет читать ключ с клавиатуры, а затем вернуть его.

Если вы хотите что-то большее, я создал объект KeyCapture. Вы можете создать его через что-то вроде keys = keyPress.KeyCapture().

Тогда вы можете сделать три вещи:

addEvent(functionName) принимает любую функцию, которая принимает один параметр. Затем каждый раз, когда нажимается клавиша, эта функция будет вызываться с этой ключевой строкой при ее вводе. Они запускаются в отдельном потоке, поэтому вы можете блокировать все, что вам нужно, и это не испортит функциональность KeyCapturer и не задержит другие события.

get() возвращает ключ тем же способом блокировки, что и раньше. Теперь это необходимо, потому что ключи теперь захватываются через объект KeyCapture, поэтому keyPress.getKey() будет конфликтовать с этим поведением, и оба из них будут пропускать некоторые клавиши, поскольку только один ключ может быть захвачен за раз. Кроме того, скажем, пользователь нажимает "a", затем "b", вы вызываете get(), пользователь нажимает "c" . Этот вызов get() немедленно вернет 'a', затем, если вы его снова вызовете, он вернет 'b', затем 'c'. Если вы снова назовете его, он будет заблокирован до нажатия другой клавиши. Это гарантирует, что вы не пропустите какие-либо ключи, при желании, блокируя их. Таким образом, это немного отличается от keyPress.getKey() от

Если вы хотите, чтобы поведение getKey() назад, 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() с потерями будет возвращать самую старую нажатую клавишу, не возвращенную get(). 3. Все события будут запускаться с этим ключом в качестве входных данных 4. Этот ключ будет сохранен в списке ключей getAsync(), где этот lis twill будет возвращен и установлен в пустой список при следующем вызове 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"

Он работает хорошо для меня из простого теста, который я сделал, но я с удовольствием отвечу и другие отзывы, если что-то я пропустил.

Я разместил здесь также.

Ответ 13

Комментарий в одном из других ответов упоминал режим cbreak, который важен для реализаций Unix, потому что вы, как правило, не хотите, чтобы ^ C (KeyboardError) потреблялся getchar (как это будет, когда вы устанавливаете терминал raw mode, как это сделано большинством других ответов).

Еще одна важная деталь: если вы хотите прочитать один символ, а не один байт, вы должны прочитать 4 байта из входного потока, так как максимальное количество байтов одного символа будет состоять из UTF-8 ( Python 3+). Чтение только одного байта приведет к неожиданным результатам для многобайтовых символов, таких как стрелки клавиатуры.

Здесь моя измененная реализация для Unix:

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)

Ответ 14

Попробуйте это с 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, "."

Ответ 15

Пакет curses в python может использоваться для ввода "сырого" режима для ввода символов с терминала несколькими инструкциями. Основное использование 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!')

Ответ 16

Рецепт ActiveState, похоже, содержит небольшую ошибку для систем "posix", которая предотвращает прерывание Ctrl-C (я использую Mac). Если я поместил в свой скрипт следующий код:

<Предварительно > <код > в то время как (True):   печать (Геч()) Код >

Я никогда не смогу завершить сценарий с помощью Ctrl-C, и мне нужно убить мой терминал для выхода.

Я считаю, что причиной является следующая строка, и она слишком жестока:

<Предварительно > <код > tty.setraw(sys.stdin.fileno()) Код >

Помимо этого, пакет tty не нужен, termios достаточно для его обработки.

Ниже приведен улучшенный код, который работает для меня ( Ctrl-C будет прервать), с дополнительной функцией getche, которая будет эхо по char по мере ввода:

 , если sys.platform == 'win32':   import msvcrt   getch = msvcrt.getch   getche = msvcrt.getche
еще:   import sys   условия импорта   def __gen_ch_getter (эхо):       def __fun():           fd = sys.stdin.fileno()           oldattr = termios.tcgetattr(fd)           newattr = oldattr [:]           пытаться:               если эхо:                   # отключить печать символов ctrl, в противном случае, backspace будет напечатано как "^?".                   lflag = ~ (termios.ICANON | termios.ECHOCTL)               еще:                   lflag = ~ (termios.ICANON | termios.ECHO)               newattr [3] & = lflag               termios.tcsetattr(fd, termios.TCSADRAIN, newattr)               ch = sys.stdin.read(1)               if echo и ord (ch) == 127: # backspace                   # эмулировать обратное пространство                   # https://stackoverflow.com/a/47962872/404271                   sys.stdout.write('\ b\b')           в конце концов:               termios.tcsetattr(fd, termios.TCSADRAIN, oldattr)           return ch       return __fun   getch = __gen_ch_getter (False)   getche = __gen_ch_getter (True)
Код>

Литература:

Ответ 17

Мое решение для python3, не зависящее от пакетов пакетов.

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

Ответ 18

Если я буду делать что-то сложное, я буду использовать проклятия для чтения ключей. Но много раз я просто хочу простой скрипт 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

Ответ 19

Принятый ответ мне не удался (я держал клавишу, ничего не происходило, потом нажимал другую клавишу, и она работала).

Изучив модуль curses, он действительно кажется правильным. И теперь он доступен для Windows через Windows-курсоры (доступны через pip), так что вы можете программировать независимо от платформы. Вот пример, вдохновленный этим хорошим руководством на 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) в интерактивном режиме.

Ответ 20

Ответили здесь: raw_input в python без нажатия Enter

Используйте это code-

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()  # does not 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()

Ссылка: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py

Ответ 21

Если вы хотите зарегистрировать только одну нажатие клавиши, даже если пользователь нажимал ее более одного раза или продолжал нажимать клавишу дольше. Чтобы избежать получения нескольких нажатых входов, используйте цикл 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)

Ответ 22

Встраивание 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.")

Ответ 23

Я считаю, что это одно из самых элегантных решений.

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!")