Existe-t-il un moyen de lire un seul caractère à partir de la saisie utilisateur? Par exemple, ils appuient sur une touche du terminal et celle-ci est renvoyée (un peu comme getch()
). Je sais qu’il existe une fonction dans Windows, mais j’aimerais une solution multiplate-forme.
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()
sys.stdin.read(1)
va fondamentalement lire 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()
( extrait de http://code.activestate.com/recipes/134892/ )
L'ActiveState recette cité textuellement dans deux réponses est trop technique. 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()
La bibliothèque readchar , également basée sur la recette ActiveState mentionnée dans d’autres réponses, mérite également d’être testé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é sous Windows et Linux avec Python 2.7.
Sous Windows, seules les touches correspondant à des lettres ou à des codes de contrôle ASCII sont prises en charge (Backspace, Enter, Esc, Tab, Ctrl+ lettre ). Sous GNU/Linux (en fonction du terminal exact, peut-être?), Vous obtenez également Insert, Delete, Pg Up, Pg Dn, Home, End et F n clés ... mais ensuite, il y a des problèmes séparant ces clés spéciales d'un Esc.
Avertissement: comme avec la plupart des réponses (toutes?) Ici, les touches de signalisation comme Ctrl+C, Ctrl+D et Ctrl+Z sont capturés et renvoyés (sous la forme '\x03'
, '\x04'
et '\x1a'
respectivement); votre programme peut être difficile à avorter.
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 cet article de blog .
Je pense que cela devient extrêmement maladroit à ce stade, et le débogage sur les différentes plates-formes est un véritable gâchis.
Vous feriez mieux d'utiliser quelque chose comme pyglet, pygame, cocos2d - si vous faites quelque chose de plus élaboré que cela et aurez besoin de visuels, OR malédictions si vous allez travailler avec le terminal.
Curses is standard: http://docs.python.org/library/curses.html
Ce code, basé sur ici , lèvera correctement KeyboardInterrupt et EOFError si Ctrl+C ou Ctrl+D sont pressés.
Devrait fonctionner sur 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()
La réponse (actuellement) la mieux classée (avec le code ActiveState) est excessivement compliquée. Je ne vois aucune raison d'utiliser des classes lorsqu'une simple fonction devrait suffire. Vous trouverez ci-dessous deux implémentations qui accomplissent la même chose mais avec un code plus lisible.
Ces deux implémentations:
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: éviter les importations répétées et la gestion des exceptions:
[EDIT] J'ai raté l'un des avantages du code ActiveState. Si vous prévoyez de lire les 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 des 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 (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))
Les réponses ici étaient informatives, mais je souhaitais également un moyen d'obtenir des appuis sur les touches de manière asynchrone et de déclencher les appuis sur les touches dans des événements distincts, le tout de manière multitâche et sécurisée par les threads. PyGame était aussi trop gonflé pour moi. Alors j’ai fait ce qui suit (dans Python 2.7 mais j’imagine qu’il est facilement portable), que j’ai pensé partager ici au cas où il serait utile à d’autres personnes. Je l'ai stocké 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 soit appeler simplement keyPress.getKey()
, qui lira une touche du clavier, puis la renverra.
Si vous voulez quelque chose de plus que cela, j'ai créé un objet KeyCapture
. Vous pouvez en créer un via quelque chose comme keys = keyPress.KeyCapture()
.
Ensuite, vous pouvez faire trois choses:
addEvent(functionName)
accepte toute fonction prenant un paramètre. Ensuite, chaque fois que vous appuierez sur une touche, cette fonction sera appelée avec la chaîne de cette touche en entrée. Celles-ci sont exécutées dans un thread séparé, vous pouvez donc bloquer tout ce que vous voulez et cela ne gâchera pas la fonctionnalité de KeyCapturer, ni ne retardera les autres événements.
get()
renvoie une clé de la même manière bloquante qu'avant. Il est maintenant nécessaire ici parce que les clés sont capturées via l'objet KeyCapture
, de sorte que keyPress.getKey()
serait en conflit avec ce comportement et que deux d'entre elles manqueraient certaines clés, car une seule clé peut être capturée à la fois. De plus, disons que l'utilisateur appuie sur 'a', puis 'b', vous appelez get()
, l'utilisateur appuie sur 'c'. Cet appel get()
renverra immédiatement "a", puis si vous l'appelez à nouveau, il retournera "b", puis "c". Si vous l'appelez à nouveau, il sera bloqué jusqu'à ce qu'une autre touche soit enfoncée. Cela garantit que vous ne manquerez aucune clé, de manière bloquante si vous le souhaitez. Donc, de cette façon, c'est un peu différent de keyPress.getKey()
d'avant
Si vous souhaitez que le comportement de getKey()
soit rétabli, get(lossy=True)
est semblable à get()
, sauf qu'il ne renvoie que les touches enfoncées après l'appel à get()
. Ainsi, dans l'exemple ci-dessus, get()
bloquera jusqu'à ce que l'utilisateur appuie sur "c". Si vous l'appelez à nouveau, il bloquera 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 de temps en temps et vérifie quelles touches ont été pressées. Ainsi, getAsync()
renvoie la liste de toutes les touches sur lesquelles vous avez appuyé depuis le dernier appel à getAsync()
, dans l'ordre, de l'ordre de la touche la plus ancienne à 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 []
vide sera renvoyé.
Pour commencer à capturer les clés, vous devez appeler keys.startCapture()
avec votre objet keys
créé ci-dessus. startCapture
ne bloque pas et démarre simplement un thread qui enregistre uniquement les appuis sur les touches et un autre thread pour traiter ces appuis. Il existe deux threads pour garantir que le thread qui enregistre les appuis de clé ne manque aucune clé.
Si vous souhaitez arrêter de capturer les clés, vous pouvez appeler keys.stopCapture()
et le logiciel cessera de capturer les clés. Cependant, comme capturer une clé est une opération bloquante, les clés de capture de thread peuvent capturer une clé supplémentaire après avoir appelé stopCapture()
.
Pour éviter cela, vous pouvez passer un paramètre facultatif dans startCapture(functionName, args)
d'une fonction qui ne fait que vérifier si une clé est égale à 'c', puis se fermer. Il est important que cette fonction fasse très peu de choses avant, par exemple, un sommeil ici nous fera rater des clés.
Cependant, si stopCapture()
est appelé dans cette fonction, les captures de clé seront immédiatement arrêtées, sans plus tenter de le capturer, et tous les appels get()
seront renvoyés immédiatement, avec None si Aucune touche n'a encore été enfoncée.
De même, étant donné que get()
et getAsync()
enregistrent toutes les touches précédemment enfoncées (jusqu'à ce que vous les récupériez), vous pouvez appeler clearGetList()
et clearAsyncList()
pour oublier les touches précédemment enfoncées.
Notez que get()
, getAsync()
et les événements sont indépendants. Ainsi, si une touche est enfoncée: 1. Un appel à get()
qui est en attente, avec une perte activée, renverra cette clé. Les autres appels en attente (le cas échéant) continueront d’attendre. 2. Cette clé sera stockée dans la file d'attente des clés, de sorte que get()
avec lossy désactivé renvoie la clé la plus ancienne sur laquelle vous avez appuyé et qui n'a pas encore été 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 clés getAsync()
, où cette liste sera renvoyée et définie comme 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 à partir du simple test que j'ai fait, mais je prendrai volontiers les commentaires des autres aussi si quelque chose me manquait.
J'ai posté ceci ici aussi.
Cela pourrait ê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 __== "__main__":
test()
Un commentaire dans l’une des autres réponses mentionnait 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 ce sera le cas lorsque vous définissez le terminal sur raw. mode, comme le font 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. Un seul caractère sera composé de 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 ma nouvelle implémentation 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)
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 sur une touche sans vous arrêter. it) et multiplate-forme.
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
Ceci est NON BLOQUANT, 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)
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, "."
Le package curses
dans python peut être utilisé pour entrer en mode "brut" pour la saisie de caractères à partir du terminal avec seulement quelques instructions. Curses est principalement utilisé pour remplacer l'écran par 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 savoir comment curses modifie 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!')
La recette de l'ActiveState semble contenir un petit bogue pour les systèmes "posix" empêchant Ctrl-C
de s'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 pouvoir 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 paquetage tty
n'est pas vraiment nécessaire, termios
suffit à le gérer.
Ci-dessous, le code amélioré qui fonctionne pour moi (Ctrl-C
s'interrompt), avec la fonction supplémentaire getche
qui renvoie le 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:
La réponse acceptée ne fonctionnait pas très bien pour moi (je détiendrais une touche, rien ne se passerait, puis j'appuierais sur une autre touche et cela fonctionnerait).
Après avoir entendu parler du module des malédictions , il semble que ce soit la bonne façon de procéder. Et il est maintenant disponible pour Windows via windows-cursors (disponible via pip), vous permettant ainsi de programmer de manière agnostique. Voici un exemple inspiré par ceci Nice tutorial sur YouTube:
import curses
def getkey(stdscr):
curses.curs_set(0)
while True:
key = stdscr.getch()
if key != -1:
break
return key
if __== "__main__":
print(curses.wrapper(getkey))
Enregistrez-le avec une extension .py
ou exécutez curses.wrapper(getkey)
en mode interactif.
L'installation 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.")
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())
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!")
Si je fais quelque chose de compliqué, j'utilise 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 peut lire les touches fléchées. Je le fais donc:
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