print - Python lee un solo carácter del usuario
python input() (20)
¿Hay una manera de leer un solo carácter de la entrada del usuario? Por ejemplo, presionan una tecla en el terminal y se devuelve (algo así como getch()
). Sé que hay una función en Windows para eso, pero me gustaría algo que sea multiplataforma.
Aquí hay un enlace a un sitio que dice cómo puede leer un solo carácter en Windows, Linux y 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()
Creo que se vuelve extremadamente torpe en este punto, y la depuración en las diferentes plataformas es un gran lío.
Estarás mejor usando algo como pyglet, pygame, cocos2d, si estás haciendo algo más elaborado y necesitarás imágenes o maldiciones si vas a trabajar con el terminal.
Curses es estándar: http://docs.python.org/library/curses.html
Este código, basado http://code.activestate.com/recipes/134892/ , levantará correctamente KeyboardInterrupt y EOFError si se presionan Ctrl + C o Ctrl + D.
Debería funcionar en Windows y Linux. Una versión de OS X está disponible en la fuente original.
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()
Este podría ser un caso de uso para un administrador de contexto. Dejando a un lado las concesiones para el sistema operativo Windows, he aquí mi sugerencia:
#!/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()
Esto es NO BLOQUEO, lee una clave y la almacena en 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
en tu programa
keypress = Keypress()
while something:
do something
if keypress == ''c'':
break
elif keypress == ''i'':
print(''info'')
else:
print("i dont understand %s" % keypress)
Intente usar esto: http://home.wlu.edu/~levys/software/kbhit.py Es no bloqueante (eso significa que puede tener un bucle while y detectar una pulsación de tecla sin detenerlo) y multiplataforma.
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 ejemplo para usar esto:
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()
O puedes usar el módulo getch de PyPi . Pero esto bloquearía el bucle while
La http://code.activestate.com/recipes/134892/ ActiveState citada textualmente en dos respuestas está sobre diseñada. Se puede reducir a esto:
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 respuesta mejor clasificada (actualmente) (con el código ActiveState) es demasiado complicada. No veo una razón para usar clases cuando una mera función debería ser suficiente. A continuación se muestran dos implementaciones que logran lo mismo pero con un código más legible.
Ambas implementaciones:
- funciona bien en Python 2 o Python 3
- Trabajar en Windows, OSX y Linux.
- lee solo un byte (es decir, no esperan una nueva línea)
- no dependa de ninguna biblioteca externa
- son autónomos (sin código fuera de la definición de la función)
Versión 1: legible y 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
Versión 2: evitar las importaciones repetidas y el manejo de excepciones:
[EDITAR] Me perdí una ventaja del código ActiveState. Si planea leer caracteres varias veces, ese código evita el costo (despreciable) de repetir la importación de Windows y el manejo de excepciones ImportError en sistemas similares a Unix. Si bien probablemente debería preocuparse más por la legibilidad del código que esa optimización insignificante, aquí hay una alternativa (es similar a la respuesta de Louis, pero getChar () es independiente) que funciona igual que el código ActiveState y es más legible:
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()
Código de ejemplo que ejerce cualquiera de las versiones de getChar () anteriores:
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))
Las respuestas here fueron informativas, sin embargo, también quería una manera de presionar las teclas de forma asíncrona y disparar las teclas en eventos separados, todo de una manera segura para subprocesos y multiplataforma. PyGame también estaba demasiado hinchado para mí. Así que hice lo siguiente (en Python 2.7, pero sospecho que es fácilmente portátil), que pensé que compartiría aquí en caso de que fuera útil para alguien más. Guardé esto en un archivo llamado 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://.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()
La idea es que simplemente puede llamar a keyPress.getKey()
, que leerá una tecla del teclado y luego la devolverá.
Si quieres algo más que eso, hice un objeto KeyCapture
. Puede crear uno a través de algo como keys = keyPress.KeyCapture()
.
Luego hay tres cosas que puedes hacer:
addEvent(functionName)
toma cualquier función que tome un parámetro. Luego, cada vez que se presione una tecla, se llamará a esta función con la cadena de esa tecla como entrada. Estos se ejecutan en un hilo separado, por lo que puede bloquear todo lo que quiera en ellos y no arruinará la funcionalidad del KeyCapturer ni retrasará los otros eventos.
get()
devuelve una clave de la misma forma de bloqueo que antes. Ahora es necesario aquí porque las claves se capturan a través del objeto KeyCapture
ahora, por lo que keyPress.getKey()
entraría en conflicto con ese comportamiento y ambas perderían algunas claves ya que solo se puede capturar una clave a la vez. Además, diga que el usuario presiona ''a'', luego ''b'', usted llama a get()
, el usuario presiona ''c''. Esa llamada get()
devolverá inmediatamente ''a'', luego, si la llama nuevamente, devolverá ''b'', luego ''c''. Si lo llama de nuevo, se bloqueará hasta que se presione otra tecla. Esto asegura que no se pierda ninguna clave, de forma bloqueada si se desea. Así que de esta manera es un poco diferente a keyPress.getKey()
de antes
Si desea que el comportamiento de getKey()
vuelva, get(lossy=True)
es como get()
, excepto que solo devuelve las teclas presionadas después de la llamada para get()
. Entonces, en el ejemplo anterior, get()
bloquearía hasta que el usuario presione ''c'', y luego, si lo llama nuevamente, bloqueará hasta que se presione otra tecla.
getAsync()
es un poco diferente. Está diseñado para algo que procesa mucho, y luego, ocasionalmente, vuelve y comprueba qué teclas se presionaron. Por getAsync()
tanto, getAsync()
devuelve una lista de todas las teclas presionadas desde la última llamada a getAsync()
, en orden desde la tecla más antigua presionada hasta la tecla más reciente presionada. Tampoco se bloquea, lo que significa que si no se han presionado teclas desde la última llamada a getAsync()
, se getAsync()
un []
vacío.
Para comenzar a capturar claves, debe llamar a keys.startCapture()
con el objeto de keys
que se creó anteriormente. startCapture
no es bloqueante, y simplemente inicia un hilo que simplemente registra las pulsaciones de teclas, y otro hilo para procesar esas pulsaciones de teclas. Hay dos hilos para garantizar que el hilo que registra las pulsaciones de teclas no se pierda ninguna tecla.
Si desea detener la captura de claves, puede llamar a keys.stopCapture()
y dejará de capturar las claves. Sin embargo, dado que la captura de una clave es una operación de bloqueo, las claves de captura de subprocesos pueden capturar una clave más después de llamar a stopCapture()
.
Para evitar esto, puede pasar uno o startCapture(functionName, args)
parámetros opcionales a startCapture(functionName, args)
de una función que solo hace algo como verificar si una tecla es igual a ''c'' y luego sale. Es importante que esta función haga muy poco antes, por ejemplo, una suspensión aquí nos hará perder las teclas.
Sin embargo, si se llama a stopCapture()
en esta función, las capturas de las teclas se detendrán inmediatamente, sin intentar capturar más, y todas las llamadas get()
se devolverán de inmediato, con Ninguna si no se han presionado aún las teclas.
Además, dado que get()
y getAsync()
almacenan todas las teclas anteriores presionadas (hasta que las recuperas), puedes llamar a clearGetList()
y clearAsyncList()
para olvidar las teclas presionadas previamente.
Tenga en cuenta que get()
, getAsync()
y los eventos son independientes, por lo que si se presiona una tecla: 1. Una llamada a get()
que está en espera, con la pérdida activada, devolverá esa tecla. Las otras llamadas en espera (si las hay) continuarán esperando. 2. Esa clave se almacenará en la cola de las claves de obtención, de modo que get()
con pérdida desactivada devolverá la clave más antigua que no haya sido devuelta por get()
todavía. 3. Todos los eventos se activarán con esa clave como entrada. 4. Esa clave se almacenará en la lista de getAsync()
de getAsync()
, donde se devolverá esa lista y se establecerá en una lista vacía en la próxima llamada a getAsync()
Si todo esto es demasiado, aquí hay un ejemplo de un caso de uso:
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"
Me está funcionando bien con la prueba simple que realicé, pero con mucho gusto recibiré también los comentarios de otros si hay algo que no haya visto.
He publicado esto here también.
También vale la pena probar la biblioteca readchar , que en parte se basa en la receta ActiveState mencionada en otras respuestas.
Instalación:
pip install readchar
Uso:
import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))
Probado en Windows y Linux con Python 2.7.
En Windows, solo se admiten las teclas que se asignan a letras o códigos de control ASCII ( Retroceso , Intro , Esc , Tab , Ctrl + letra ). En GNU / Linux (dependiendo de la terminal exacta, ¿quizás?) También obtiene las teclas Insertar , Eliminar , Pg Up , Pg Dn , Inicio , Fin y F n ... pero luego, hay problemas que separan estas teclas especiales de una Esc .
Advertencia: como con la mayoría de las respuestas (¿todas?) Aquí, las teclas de señal como Ctrl + C , Ctrl + D y Ctrl + Z se capturan y devuelven (como ''/x03''
, ''/x04''
y ''/x1a''
respectivamente); Su programa puede ser difícil de abortar.
Un método alternativo:
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 esta entrada de blog .
Creo que esta es una de las soluciones más elegantes.
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
y luego usarlo en el código:
if getch() == chr(ESC_ASCII_VALUE):
print("ESC!")
El build-in raw_input debería ayudar.
for i in range(3):
print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")
Mi solución para python3, no depende de ningún paquete 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())
Prueba esto con 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, "."
El curses
paquete en python se puede usar para ingresar al modo "en bruto" para la entrada de caracteres desde el terminal con solo unas pocas declaraciones. El uso principal de Curses es controlar la pantalla para la salida, que puede no ser lo que usted desea. Este fragmento de código usa print()
sentencias en su lugar, que son utilizables, pero debe ser consciente de cómo las cursas cambian los finales de línea asociados a la salida.
#!/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 receta de ActiveState parece contener un pequeño error para los sistemas "posix" que evita Ctrl-C
interrumpir (estoy usando Mac). Si pongo el siguiente código en mi script:
while(True):
print(getch())
Nunca podré terminar el script con Ctrl-C
, y tengo que matar mi terminal para escapar.
Creo que la siguiente línea es la causa, y también es demasiado brutal:
tty.setraw(sys.stdin.fileno())
Aparte de eso, el paquete tty
no es realmente necesario, termios
es suficiente para manejarlo.
A continuación se muestra el código mejorado que funciona para mí ( Ctrl-C
se interrumpirá), con la getche
función adicional que hace eco del carácter a medida que escribe:
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://.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)
Referencias:
Si estoy haciendo algo complicado usaré maldiciones para leer claves. Pero muchas veces solo quiero un script de Python 3 que use la biblioteca estándar y pueda leer las teclas de flecha, así que hago esto:
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
Un comentario en una de las otras respuestas mencionó el modo cbreak, que es importante para las implementaciones de Unix porque generalmente no quiere que ^ (( KeyboardError
) lo consuma G) (como lo hará cuando configure el terminal en modo crudo, como lo hizo la mayoría de las otras respuestas).
Otro detalle importante es que si desea leer un carácter y no un byte , debe leer 4 bytes de la secuencia de entrada, ya que ese es el número máximo de bytes en que estará compuesto un solo carácter en UTF-8 (Python 3+ ). La lectura de un solo byte producirá resultados inesperados para caracteres de varios bytes, como las flechas del teclado.
Aquí está mi implementación modificada para 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)
sys.stdin.read(1)
Básicamente leerá 1 byte de STDIN.
Si debe usar el método que no espera al /n
, puede usar este código como se sugiere en la respuesta anterior:
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()
( tomado de http://code.activestate.com/recipes/134892/ )