Python lit un seul caractère de l'utilisateur

Existe-t-il un moyen de lire un seul caractère à partir de l'entrée de l'utilisateur? Par exemple, ils appuient sur une touche au terminal et elle est renvoyée (un peu comme getch()). Je sais qu'il y a une fonction dans Windows pour cela, mais je voudrais quelque chose qui est multi-plateforme.

209
demandé sur Masked Man 2009-02-04 10:08:03

20 réponses

Voici un lien vers un site qui indique comment vous pouvez lire un seul caractère sous Windows, Linux et 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()
152
répondu tehvan 2016-01-02 02:34:19
sys.stdin.read(1)

Lira essentiellement 1 octet de STDIN.

Si vous devez utiliser la méthode qui n'attend pas le \n, Vous pouvez utiliser ce code comme suggéré dans la réponse précédente:

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

(prises de http://code.activestate.com/recipes/134892/)

65
répondu Yuval Adam 2012-10-18 13:16:28

La recette ActiveState Citée verbatim dans deux réponses est sur-conçue. Il peut être réduit à ceci:

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()
54
répondu Louis 2014-02-09 13:27:42

La bibliothèque readchar, qui est en partie basée sur la recette ActiveState mentionnée dans d'autres réponses, vaut également la peine d'être essayée.

Installation:

pip install readchar

Utilisation:

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

Testé sous Windows et Linux avec Python 2.7.

Sur Windows, uniquement des clés qui correspondent à des lettres ou ASCII codes de contrôle sont pris en charge (retour arrière, Entrez, Esc, Onglet, Ctrl+lettre). Sous GNU / Linux (selon le terminal exact, peut-être?) vous bénéficiez également de Insérer, Supprimer, Pg Jusqu', Pg Dn, la Maison, End et F n les touches... mais alors, il y a des problèmes séparant ces clés spéciales d'un Esc .

Mise en garde: comme avec la plupart (tous?) les réponses ici, le signal des touches de Ctrl+C, Ctrl+D et Ctrl+Z sont pris et retourné (comme '\x03', '\x04' et '\x1a', respectivement); votre le programme peut être difficile à abandonner.

38
répondu Søren Løvborg 2014-08-16 18:47:41

Une autre méthode:

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

De Cet article de blog .

14
répondu Tyler 2012-05-22 00:13:54

Je pense que cela devient extrêmement maladroit à ce stade, et le débogage sur les différentes plates-formes est un gros gâchis.

Vous feriez mieux d'utiliser quelque chose comme pyglet, pygame, cocos2d - si vous faites quelque chose de plus élaboré que ce et aura besoin de visuels, OU malédictions si vous allez travailler avec le terminal.

Malédictions de série: http://docs.python.org/library/curses.html

12
répondu nachik 2009-02-04 11:04:26

Ce code, basé sur ici, seront correctement élever KeyboardInterrupt et EOFError si Ctrl+C ou Ctrl+D sont pressés.

Devrait fonctionner sous Windows et Linux. Une version OS X est disponible à partir de la source d'origine.

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
répondu kiri 2014-01-01 05:13:33

La réponse (actuellement) la mieux classée (avec le code ActiveState) est trop compliquée. Je ne vois pas de raison d'utiliser des classes quand une simple fonction devrait suffire. Voici deux implémentations qui accomplissent la même chose mais avec un code plus lisible.

Ces Deux implémentations:

  1. fonctionne très bien en Python 2 ou Python 3
  2. fonctionne sous Windows, OSX et Linux
  3. Lisez juste un octet (c'est-à-dire qu'ils n'attendent pas une nouvelle ligne)
  4. ne fais pas ça dépend de toutes les bibliothèques externes
  5. sont autonomes (pas de code en dehors de la définition de la fonction)

Version 1: lisible et simple

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

Version 2: évitez les importations répétées et la gestion des exceptions:

[EDIT] j'ai manqué un avantage du code ActiveState. Si vous prévoyez de lire des caractères plusieurs fois, ce code évite le coût (négligeable) de la répétition de L'importation Windows et de la gestion des exceptions ImportError Les systèmes de type Unix. Alors que vous devriez probablement être plus préoccupé par la lisibilité du code que cette optimisation négligeable, voici une alternative (elle est similaire à la réponse de Louis, mais getChar () est autonome) qui fonctionne de la même manière que le code ActiveState et est plus lisible:

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

Exemple de code qui exerce l'une des versions de getChar() ci-dessus:

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))
6
répondu Matthew Strax-Haber 2017-08-24 07:03:30

Cela pourrait être un cas d'utilisation pour un gestionnaire de contexte. En laissant de côté les allocations pour Windows OS, voici ma suggestion:

#!/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()
4
répondu Alex Kleider 2014-09-28 20:24:21

Les réponses ici {[43] } étaient informatives, mais je voulais aussi un moyen d'obtenir des pressions de touches de manière asynchrone et de déclencher des pressions de touches dans des événements séparés, le tout de manière multi-plateforme. PyGame était aussi trop gonflé pour moi. J'ai donc fait ce qui suit (en python 2.7 mais je soupçonne qu'il est facilement portable), que je pensais partager ici au cas où cela serait utile pour quelqu'un d'autre. J'ai stocké ceci dans un fichier nommé 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()

, L'idée est que vous pouvez simplement appeler keyPress.getKey(), qui lira une touche du clavier, puis la renverra.

Si vous voulez quelque chose de plus que cela, j'ai fait un objet KeyCapture. Vous pouvez en créer un via quelque chose comme keys = keyPress.KeyCapture().

Ensuite, il y a trois choses que vous pouvez faire:

addEvent(functionName) prend dans une fonction qui prend en paramètre. Ensuite, chaque fois qu'une touche est pressée, cette fonction sera appelée avec la chaîne de cette clé en entrée. Ceux-ci sont exécutés dans un thread séparé, de sorte que vous pouvez bloquer tout ce que vous voulez en eux et cela ne gâchera pas up la fonctionnalité du KeyCapturer ni retarder les autres événements.

get() renvoie une clé de la même manière de blocage qu'auparavant. Il est maintenant nécessaire ici parce que les clés sont capturées via l'objet KeyCapture Maintenant, donc keyPress.getKey() serait en conflit avec ce comportement et les deux manqueraient certaines clés car une seule clé peut être capturée à la fois. Aussi, dire que l'utilisateur appuie sur " a "puis " b", vous appelez get(), l'utilisateur appuie sur "c". Cet appel get() renverra immédiatement 'a' , alors si vous l'appelez encore une fois, il retournera 'b', puis 'c'. Si vous l'appelez à nouveau, il bloquera jusqu'à ce qu'une autre touche soit enfoncée. Cela garantit que vous ne manquez pas de clés, de manière bloquante si vous le souhaitez. Donc, dans cette façon c'est un peu différent de keyPress.getKey() avant

Si vous voulez le comportement de getKey() retour, get(lossy=True), c'est comme get(), sauf qu'il renvoie uniquement les touches après l'appel de get(). Donc, dans l'exemple ci-dessus, get() bloquerait jusqu'à ce que l'utilisateur appuie sur 'c', et si vous l'appelez à nouveau, il le fera bloquer jusqu'à ce qu'une autre touche soit enfoncée.

getAsync() est un peu différente. Il est conçu pour quelque chose qui fait beaucoup de traitement, puis revient de temps en temps et vérifie quelles touches ont été pressées. Ainsi, getAsync() renvoie une liste de toutes les touches pressées depuis le dernier appel à getAsync(), dans l'ordre de la touche la plus ancienne à la touche la plus récente. Il ne bloque pas non plus, ce qui signifie que si aucune touche n'a été pressée depuis le dernier appel à getAsync(), Un [] vide sera retourné.

À en fait, commencez à capturer des clés, vous devez appeler keys.startCapture() avec votre objet keys fait ci-dessus. {[24] } est non bloquant, et démarre simplement un thread qui enregistre simplement les pressions de touche, et un autre thread pour traiter ces pressions de touche. Il y a deux threads pour s'assurer que le thread qui enregistre les touches ne manque aucune clé.

Si vous voulez arrêter de capturer des clés, vous pouvez appeler keys.stopCapture() et cela arrêtera de capturer des clés. Cependant, depuis la capture d'une clé est une opération de blocage, le thread les clés de capture peuvent capturer une clé de plus après avoir appelé stopCapture().

Pour éviter cela, vous pouvez passer un paramètre optionnel dans startCapture(functionName, args) d'une fonction qui fait juste quelque chose comme vérifier si une clé est égale à 'c', puis quitte. Il est important que cette fonction fasse très peu avant, par exemple, un sommeil ici nous fera manquer des clés.

Cependant, si stopCapture() est appelé dans cette fonction, les captures de clés seront arrêtées immédiatement, sans essayer de capturer plus, et que tout get() les appels seront renvoyés immédiatement, avec None si aucune touche n'a encore été enfoncée.

Aussi, depuis get() et getAsync() stocker toutes les touches enfoncées (jusqu'à ce que vous les récupérer), vous pouvez appeler clearGetList() et clearAsyncList() oublier les clés préalablement pressé.

Notez que get(), getAsync() et les événements sont indépendants, donc si une touche est enfoncée: 1. Un appel à get() qui est en attente, avec perte, reviendra cette clé. Les autres appels en attente (le cas échéant) continueront à attendre. 2. Cette clé sera stocké dans la file d'attente des clés get, de sorte que get() avec lossy off retournera la touche la plus ancienne enfoncée qui n'est pas encore retournée par get(). 3. Tous les événements seront déclenchés avec cette clé comme entrée 4. Cette clé sera stockée dans la liste des getAsync() clés, où cette lis sergé être retourné et mis à la liste vide sur le prochain appel à getAsync()

, Si tout cela est trop, voici un exemple de cas d'utilisation:

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"

Cela fonctionne bien pour moi à partir du simple test que j'ai fait, mais je serai heureux d'en prendre d'autres commentaires aussi bien s'il y a quelque chose que j'ai manqué.

J'ai posté ce ici ainsi.

4
répondu Phylliida 2017-05-23 12:10:41

Ceci est non bloquant, lit une clé et la stocke dans keypress.clé.

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

Dans votre programme

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)
3
répondu Davoud Taghawi-Nejad 2014-06-22 21:03:03

Essayez d'utiliser ceci: http://home.wlu.edu/~levys/software/kbhit.py Il est non bloquant (cela signifie que vous pouvez avoir une boucle while et détecter une touche sans l'arrêter) et multi-plateforme.

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

Exemple pour utiliser ceci:

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

Ou vous pouvez utiliser le module getch de PyPi . Mais cela bloquerait la boucle while

3
répondu jdev6 2017-04-28 20:40:23

Essayez ceci avec 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, "."
2
répondu PyGuy 2016-04-03 14:08:57

Un commentaire dans l'une des autres réponses mentionne le mode cbreak, ce qui est important pour les implémentations Unix car vous ne voulez généralement pas que ^c (KeyboardError) soit consommé par getchar (comme cela se fera lorsque vous définissez le terminal en mode brut, comme cela se fait par la plupart des autres réponses).

Un autre détail important est que si vous cherchez à lire un caractère et pas un octet , Vous devriez lire 4 octets du flux d'entrée, car c'est le nombre maximum d'octets qu'un seul caractère va se composent de en UTF-8 (Python 3+). La lecture d'un seul octet produira des résultats inattendus pour les caractères multi-octets tels que les flèches du clavier.

Voici mon implémentation modifiée pour 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)
2
répondu Noah 2017-07-23 15:17:19

Le paquet curses en python peut être utilisé pour entrer en mode " raw " pour l'entrée de caractères du terminal avec seulement quelques instructions. Malédictions' utilisation principale est de prendre en charge l'écran pour la sortie, ce qui peut ne pas être ce que vous voulez. Cet extrait de code utilise à la place des instructions print(), qui sont utilisables, mais vous devez être conscient de la façon dont curses change les fins de ligne attachées à la sortie.

#!/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!')
1
répondu John Mark 2014-12-29 17:38:01

, Le raw_input devrait aider.

for i in range(3):
    print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")
0
répondu Mabooka 2015-12-15 20:51:36

Ma solution pour python3, ne dépendant d'Aucun paquet pip.

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

    print(question,prompt,flush= True,end=" ")
    oldttysettings = tty.tcgetattr(sys.stdin.fileno())
    try:
        tty.setraw(sys.stdin.fileno())
        return not sys.stdin.read(1).lower() in other_answers
    except:
        return default
    finally:
        tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)
        sys.stdout.write("\r\n")
        tty.tcdrain(sys.stdin.fileno())
0
répondu xro 2016-01-28 01:24:52

Je crois que c'est l'une des solutions les plus élégantes.

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

Puis utilisez-le dans le code:

if getch() == chr(ESC_ASCII_VALUE):
    print("ESC!")
0
répondu theAlse 2017-11-29 09:14:25

La recette de L'ActiveState semble contenir un petit bug pour les systèmes" posix " qui empêche Ctrl-C d'interrompre (j'utilise Mac). Si je mets le code suivant dans mon script:

while(True):
    print(getch())

Je ne pourrai jamais terminer le script avec Ctrl-C, et je dois tuer mon terminal pour m'échapper.

Je crois que la ligne suivante est la cause, et c'est aussi trop brutal:

tty.setraw(sys.stdin.fileno())

En dehors de cela, le paquet tty n'est pas vraiment nécessaire, termios est suffisant pour le gérer.

Ci-dessous est-ce que le code amélioré qui fonctionne pour moi (Ctrl-C va interrompre), avec la fonction supplémentaire getche qui fait écho au caractère lorsque vous tapez:

if sys.platform == 'win32':
    import msvcrt
    getch = msvcrt.getch
    getche = msvcrt.getche
else:
    import sys
    import termios
    def __gen_ch_getter(echo):
        def __fun():
            fd = sys.stdin.fileno()
            oldattr = termios.tcgetattr(fd)
            newattr = oldattr[:]
            try:
                if echo:
                    # disable ctrl character printing, otherwise, backspace will be printed as "^?"
                    lflag = ~(termios.ICANON | termios.ECHOCTL)
                else:
                    lflag = ~(termios.ICANON | termios.ECHO)
                newattr[3] &= lflag
                termios.tcsetattr(fd, termios.TCSADRAIN, newattr)
                ch = sys.stdin.read(1)
                if echo and ord(ch) == 127: # backspace
                    # emulate backspace erasing
                    # https://stackoverflow.com/a/47962872/404271
                    sys.stdout.write('\b \b')
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, oldattr)
            return ch
        return __fun
    getch = __gen_ch_getter(False)
    getche = __gen_ch_getter(True)

Références:

0
répondu ibic 2018-01-07 12:07:51

Si je fais quelque chose de compliqué, j'utiliserai malédictions pour lire les clés. Mais beaucoup de fois, je veux juste un simple script Python 3 qui utilise la bibliothèque standard et peut lire les touches fléchées, donc je fais ceci:

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
0
répondu qel 2018-09-24 05:20:12