Python a lu un seul caractère à l'utilisateur

262

Existe-t-il un moyen de lire un seul caractère de l'entrée utilisateur? Par exemple, ils appuient sur une touche du terminal et celle-ci est retournée (un peu comme getch()). Je sais qu'il y a une fonction dans Windows pour cela, mais j'aimerais quelque chose qui soit multiplateforme.

Evan Fosmark
la source
1
Sous Windows, j'ai rencontré le même problème que dans cette question . La solution est de remplacer le msvcrt.getchpar msvcrt.getwch, comme suggéré ici.
A. Roy
La solution consiste à installer le module getch "pip install getch". Pour Python2, utilisez la commande "pip2 install files.pythonhosted.org/packages/56/f7/… ". Cette solution fonctionne également dans Termux (Android).
Petr Mach

Réponses:

190

Voici un lien vers un site qui explique comment 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()
tehvan
la source
18
le code semble suffisamment court pour que vous puissiez l'inclure, mais +1 pour trouver une bonne réponse (multiplateforme) si rapidement.
John Mulder
4
Gère-t-il bien les lettres non latines (par exemple cyrilliques)? J'ai un problème avec ça et je n'arrive pas à savoir si c'est mon erreur ou non.
Phlya
7
Je n'aime pas la façon dont l' ImportErrorexception est utilisée comme une sorte de déclaration if; pourquoi ne pas appeler platform.system () pour vérifier le système d'exploitation?
Seismoid
10
@Seismoid: Demander pardon est généralement considéré comme meilleur, voir stackoverflow.com/questions/12265451/…
dirkjot
4
Ne fonctionne pas sur OS X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25, 'ioctl inapproprié pour le périphérique')"
Nom d'affichage
80
sys.stdin.read(1)

va essentiellement lire 1 octet de STDIN.

Si vous devez utiliser la méthode qui n'attend pas le, \nvous 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()

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

Yuval Adam
la source
34
Je trouve étrange que sys.stdin.read (1) attend un \ n, lol. Merci pour la soumission, cependant.
Evan Fosmark
3
Un caractère ou un octet? Ce n'est pas pareil.
chryss
4
@Evan, c'est parce que python est en mode tampon en ligne par défaut
John La Rooy
3
@EvanFosmark: ce n'est pas nécessairement que sys.stdin.read (1) attend \ n, c'est que le programme terminal décidant quand envoyer d'autres caractères à votre programme ne les écrit pas jusqu'à ce qu'il voit '\ n' - sinon comment vous pouvez appuyer sur retour arrière et corriger ce que vous tapez? (la réponse sérieuse à cela est - apprenez au programme python à implémenter le contrôle de ligne, à garder un tampon, à traiter les espaces arrière, mais c'est un monde différent dans lequel vous ne voudrez peut-être pas acheter simplement en "lisant un caractère" et pourrait faire votre ligne gestion différente de tous les autres programmes de votre système.)
Tony Delroy
2
@Seismoid EAFP
vaultah
70

La recette ActiveState citée textuellement dans deux réponses est sur-conçue. Cela peut se résumer à 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()
Louis
la source
Agréable. Mais cela lira également le premier caractère de KeyboardInterrupt (Ctrl + C), et le code a la possibilité de sortir avec 0.
user3342816
51

La bibliothèque readchar , également basée sur la recette ActiveState mentionnée dans d'autres réponses, mérite également d'être essayée .

Installation:

pip install readchar

Usage:

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

Testé sur Windows et Linux avec Python 2.7.

Sous Windows, seules les clés qui tracent des lettres ou des codes de contrôle ASCII sont pris en charge ( Backspace, Enter, Esc, Tab, Ctrl+ lettre ). Sur GNU / Linux ( en fonction de la borne exacte, peut-être?) , Vous obtenez également Insert, Delete, Pg Up, Pg Dn, Home, Endet les clés ... mais, il y a des problèmes qui séparent ces touches spéciales d'un .F nEsc

Mise en garde: comme avec la plupart (toutes?) Des réponses ici, les touches de signal comme Ctrl+ C, Ctrl+ Det Ctrl+ Zsont capturées et retournées (comme '\x03', '\x04'et '\x1a'respectivement); votre programme peut être difficile à abandonner.

Søren Løvborg
la source
3
Fonctionne également avec Python 3 sous Linux. Beaucoup mieux que getch, car readchar permet d'imprimer sur stdout en attendant la clé (via des threads ou asyncio).
wrobell
Testé sur Win10 + Python 3.5: ERREUR: root: 'dans <chaîne>' requiert une chaîne comme opérande gauche, pas des octets Traceback (dernier appel le plus récent): Fichier ".. \ main.py", ligne 184, dans le résultat de l'encapsuleur = func (* args, ** kwargs) Fichier "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", ligne 41, en readch_eg print (readchar.readchar ()) Fichier "C: \ Users \ ipcjs \ AppData \ Local \ Programs \ Python \ Python35 \ lib \ site-packages \ readchar \ readchar_windows.py ", ligne 14, dans readchar tandis que ch dans '\ x00 \ xe0': TypeError: 'in <string>' requiert une chaîne comme opérande gauche , pas d'octets
ipcjs
@ipcjs veuillez signaler ce bogue aux responsables
Melih Yıldız '
1
C'est la meilleure réponse. ajouter une dépendance à la bibliothèque VS C ++ juste pour cette fonctionnalité est fou.
FistOfFury
18

Une méthode alternative:

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 ce billet de blog .

Tyler
la source
Ne semble pas fonctionner pour moi - renvoie une chaîne vide immédiatement lors de l'appel. Sur Linux avec Python 3.6.
Marein
1
@Marein Si vous voulez qu'il se bloque (attendez la saisie), supprimez le | os.O_NONBLOCK. Sinon, vous pouvez le mettre en boucle (bonne idée de dormir un peu dans la boucle pour éviter de tourner).
Chris Gregg
En Python, il vaut mieux utiliser while Truealors while 1.
Anonyme
10

Ce code, basé ici , augmentera correctement KeyboardInterrupt et EOFError si vous appuyez sur Ctrl+ Cou Ctrl+ D.

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()
kiri
la source
7

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 alors qu'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. travailler sur Windows, OSX et Linux
  3. lire un seul octet (c'est-à-dire qu'ils n'attendent pas de nouvelle ligne)
  4. ne dépend pas de 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 raté 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 sur les systèmes de type Unix. Alors que vous devriez probablement être plus préoccupé par la lisibilité du code que par 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))
Matthew Strax-Haber
la source
2
J'ai rencontré un problème avec tty.setraw () lors de l'impression de messages en attendant simultanément une clé (multi-thread). Pour faire court, j'ai trouvé que l'utilisation de tty.setcbreak () vous permet d'obtenir un seul personnage sans casser tous les autres trucs normaux. Longue histoire dans cette réponse
TheDavidFactor
4

Cela peut être un cas d'utilisation pour un gestionnaire de contexte. 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()
Alex Kleider
la source
Vous pouvez également revenir self en __enter__et une readméthode qui retourne sys.stdin.read(1), alors vous pouvez lire plusieurs caractères dans un contexte.
L3viathan
4

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

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

Un 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

jdev6
la source
3

Ceci est NON BLOCANT, lit une clé et la stocke dans 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

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)
Davoud Taghawi-Nejad
la source
1
@ThorSummoner: Ce code a un certain nombre de problèmes - donc non , il ne fonctionnera pas pour les applications en ligne de commande.
martineau
Il s'exécute pour une application en ligne de commande, étant donné que le gestionnaire de fenêtres est en cours d'exécution.
Davoud Taghawi-Nejad
Non, il ne fonctionne pas dans un système d'exploitation sans tête. Mais il s'exécute dans une fenêtre de ligne de commande.
Davoud Taghawi-Nejad
3

Les réponses ici étaient informatives, mais je voulais également un moyen d'obtenir des pressions de touches de manière asynchrone et de déclencher des pressions de touches dans des événements distincts, le tout de manière thread-safe et multiplateforme. PyGame était également trop gonflé pour moi. J'ai donc fait ce qui suit (en Python 2.7 mais je pense qu'il est facilement portable), que j'ai pensé partager ici au cas où cela serait utile pour quelqu'un d'autre. J'ai stocké cela 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 ça, j'ai fait un KeyCaptureobjet. Vous pouvez en créer un via quelque chose comme keys = keyPress.KeyCapture().

Ensuite, vous pouvez faire trois choses:

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

get()renvoie une clé de la même manière que précédemment. Il est maintenant nécessaire ici car les clés sont capturées via l' KeyCaptureobjet 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. Supposons également que l'utilisateur appuie sur «a», puis sur «b», vous appelez get(), l'utilisateur appuie sur «c». Cet get()appel renverra immédiatement «a», puis si vous l'appelez à nouveau, il renverra «b», puis «c». Si vous l'appelez à nouveau, il se bloquera jusqu'à ce qu'une autre touche soit enfoncée. Cela garantit que vous ne manquez aucune clé, de manière bloquante si vous le souhaitez. Donc, de cette façon, c'est un peu différent keyPress.getKey()d'avant

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

getAsync()est un peu différent. Il est conçu pour quelque chose qui fait beaucoup de traitement, puis revient parfois et vérifie quelles touches ont été enfoncées. getAsync()Retourne ainsi une liste de toutes les touches enfoncé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é enfoncée depuis le dernier appel à getAsync(), un espace vide []sera retourné.

Pour commencer à capturer les clés, vous devez appeler keys.startCapture()avec votre keysobjet ci-dessus. startCapturen'est pas bloquant et démarre simplement un thread qui enregistre simplement les pressions de touches et un autre thread pour traiter ces pressions de touches. Il existe deux threads pour garantir que le thread qui enregistre les pressions de touches ne manque aucune touche.

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

Pour éviter cela, vous pouvez passer un ou plusieurs paramètres facultatifs dans startCapture(functionName, args)une fonction qui fait juste quelque chose comme vérifier si une clé est égale à «c», puis se ferme. Il est important que cette fonction fasse très peu avant, par exemple, un sommeil ici nous fera manquer des touches.

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

De plus, puisque get()et getAsync()mémorisez toutes les touches précédentes enfoncées (jusqu'à ce que vous les récupériez), vous pouvez appeler clearGetList()et clearAsyncList()oublier les touches précédemment enfoncées.

Notez que get(), getAsync()et les événements sont indépendants, donc si une touche est enfoncée: 1. Un appel à get()celui en attente, avec perte activée, renverra cette touche. Les autres appels en attente (le cas échéant) continueront à attendre. 2. Cette clé sera stockée dans la file d'attente des clés get, de sorte qu'en cas get()de perte désactivée, la clé la plus ancienne appuyée ne soit pas encore retournée 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 liste sera retournée et définie sur une liste vide lors du prochain appel àgetAsync()

Si tout cela est trop, voici un exemple 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 d'après le test simple que j'ai fait, mais je serai ravi de prendre également en compte les commentaires des autres s'il y a quelque chose qui m'a manqué.

Je l'ai également posté ici .

Phylliida
la source
3

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

Un autre détail important est que si vous cherchez à lire un caractère et non un octet , vous devez lire 4 octets dans le flux d'entrée, car c'est le nombre maximal d'octets qu'un seul caractère comprendra 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)
Noé
la source
2

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, "."
PyGuy
la source
C'est une bonne idée, mais cela ne fonctionne pas sur la ligne de commande: pygame.error: video system not initialized
dirkjot
2

La recette de l'ActiveState semble contenir un petit bug pour les systèmes "posix" qui empêche l' Ctrl-Cinterruption (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 en est la cause, et c'est aussi trop brutal:

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

Mis à part cela, le package ttyn'est pas vraiment nécessaire, il termiossuffit de le gérer.

Ci-dessous est le code amélioré qui fonctionne pour moi ( Ctrl-Cinterrompra), avec la getchefonction supplémentaire qui fait écho au caractère pendant que 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:

ibic
la source
1

Le cursespackage en python peut être utilisé pour entrer en mode "brut" pour la saisie de caractères depuis le terminal avec seulement quelques instructions. L'utilisation principale de Curses est de reprendre l'écran pour la sortie, ce qui peut ne pas être ce que vous voulez. Cet extrait de code utilise des print()instructions à la place, qui sont utilisables, mais vous devez savoir comment les curses modifient 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!')
John Mark
la source
1

Si je fais quelque chose de compliqué, j'utiliserai des malédictions pour lire les clés. Mais souvent, je veux juste un simple script Python 3 qui utilise la bibliothèque standard et puisse lire les touches fléchées, alors 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
qel
la source
0

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())
xro
la source
0

Je pense 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!")
theAlse
la source
0

La réponse acceptée n'a pas très bien fonctionné pour moi (je détiendrais une touche, rien ne se passerait, puis j'appuierais sur une autre touche et cela fonctionnerait).

Après avoir appris le module curses , cela semble vraiment être la bonne voie à suivre. Et il est désormais disponible pour Windows via des curseurs Windows (disponibles via pip), vous pouvez donc programmer de manière indépendante de la plate-forme. Voici un exemple inspiré de ce joli tutoriel sur 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))

Enregistrez-le avec une .pyextension ou exécutez-le curses.wrapper(getkey)en mode interactif.

Ben Ogorek
la source
0

Répondu ici: raw_input en python sans appuyer sur Entrée

Utilisez ce 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()  # doesn't work in a while loop without it
    root.after(time_to_sleep * 1000, func=root.destroy)
    root.mainloop()
    root = None  # just in case
    return key_pressed


def __main():
        c = None
        while not c:
                c = get_key("Choose your weapon ... ", 2)
        print(c)

if __name__ == "__main__":
    __main()

Référence: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py

Meir Gabay
la source
0

Si vous ne souhaitez enregistrer qu'une seule touche, appuyez sur, même si l'utilisateur l'a appuyée plusieurs fois ou a continué d'appuyer sur la touche plus longtemps. Pour éviter d'obtenir plusieurs entrées pressées, utilisez la boucle while et passez-la.

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)
Vinay Verma
la source
0

si vous voulez simplement maintenir l'écran afin que vous puissiez voir le résultat sur le terminal, il suffit d'écrire

input()

à la fin du code et il tiendra l'écran

Khan Saad
la source
-1

Le raw_input intégré 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.")
Mabooka
la source
6
raw_input attend la touche entrée
vac