Python читает один символ от пользователя - PullRequest
230 голосов
/ 04 февраля 2009

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

Ответы [ 20 ]

168 голосов
/ 04 февраля 2009

Вот ссылка на сайт, который говорит, как вы можете прочитать один символ в 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()
71 голосов
/ 04 февраля 2009
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/)

60 голосов
/ 09 февраля 2014

Рецепт 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()
42 голосов
/ 16 августа 2014

Также стоит попробовать библиотеку 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 ( Возврат , Ввод , Esc , Tab, Ctrl + буква ). В GNU / Linux (возможно, в зависимости от конкретного терминала) вы также получаете Вставить , Удалить , Pg Up , Pg Dn , Home , End и F n ключей ... но возникают проблемы, отделяющие эти специальные ключи от Esc .

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

16 голосов
/ 31 августа 2011

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

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

Из этого блога .

12 голосов
/ 04 февраля 2009

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

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

Проклятия стандартные: http://docs.python.org/library/curses.html

8 голосов
/ 01 января 2014

Этот код, основанный на здесь , будет корректно вызывать 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()
7 голосов
/ 02 мая 2016

Ответ (в настоящее время) с самым высоким рейтингом (с кодом 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

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

[EDIT] Я пропустил одно преимущество кода ActiveState. Если вы планируете читать символы несколько раз, этот код позволяет избежать (незначительной) стоимости повторения импорта Windows и обработки исключений ImportError в Unix-подобных системах. В то время как вы, вероятно, должны быть более обеспокоены читабельностью кода, чем этой незначительной оптимизацией, вот альтернатива (она похожа на ответ Луи, но 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()

Пример кода, который работает с любой из версий 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))
4 голосов
/ 31 июля 2015

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

Идея состоит в том, что вы можете либо просто позвонить 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(). 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"

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

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

4 голосов
/ 29 сентября 2014

Это может быть сценарий использования для менеджера контекста. Оставляя в стороне допуски для ОС 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...