Python leyó un solo personaje del usuario


262

¿Hay alguna manera de leer un solo carácter de la entrada del usuario? Por ejemplo, presionan una tecla en la terminal y se devuelve (algo así como getch()). Sé que hay una función en Windows para ello, pero me gustaría algo que sea multiplataforma.


1
En Windows me encontré con el mismo problema que en esta pregunta . La solución es reemplazar el msvcrt.getchcon msvcrt.getwch, como se sugiere allí.
A. Roy

La solución es instalar el módulo getch "pip install getch". Para Python2 use el comando "pip2 install files.pythonhosted.org/packages/56/f7/… ". Esta solución también funciona en Termux (Android).
Petr Mach

Respuestas:


190

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

18
el código parece lo suficientemente corto como para que pueda incluirlo, pero +1 para encontrar una buena respuesta (multiplataforma) tan rápido.
John Mulder

44
¿Maneja bien las letras no latinas (p. Ej., Cirílicas)? Tengo un problema con eso y no puedo entender si es mi error o no.
Phlya

77
No me gusta cómo ImportErrorse usa la excepción como algún tipo de declaración if; ¿Por qué no llamar a platform.system () para verificar el sistema operativo?
Sismoid

10
@Seismoid: pedir perdón generalmente se considera mejor, ver stackoverflow.com/questions/12265451/…
dirkjot

44
No funciona en OS X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25, 'ioctl inapropiado para dispositivo')"
Nombre para mostrar

80
sys.stdin.read(1)

básicamente leerá 1 byte de STDIN.

Si debe usar el método que no espera \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/ )


34
Me resulta extraño que sys.stdin.read (1) espere a \ n, lol. Gracias por la presentación, sin embargo.
Evan Fosmark

3
¿Un personaje o un byte? Eso no es lo mismo.
chryss

44
@Evan, eso se debe a que Python está en modo de almacenamiento en línea de forma predeterminada
John La Rooy

3
@EvanFosmark: no es necesariamente que sys.stdin.read (1) espere \ n, es que el programa de terminal que decide cuándo enviar otros caracteres a su programa no los escribe hasta que ve '\ n', ¿de qué otra manera? Serás capaz de presionar la tecla de retroceso y corregir lo que estás escribiendo? (la respuesta seria a eso es: enseñar al programa de Python a implementar el control de línea, mantener un búfer, procesar espacios de retroceso, pero ese es un mundo diferente en el que quizás no quieras comprar cuando solo "lees un personaje", y podrías hacer tu línea manejo diferente de todos los otros programas en su sistema.)
Tony Delroy

2
@Seismoid EAFP
vaultah

70

La receta de ActiveState citada textualmente en dos respuestas está sobredimensionada. 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()

Agradable. Pero esto también leerá el primer carácter de KeyboardInterrupt (Ctrl + C), y el código tiene la posibilidad de salir con 0.
user3342816

51

También vale la pena probar la biblioteca readchar , que se basa en parte 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, sólo llaves que se asignan a letras o códigos de control ASCII son compatibles ( Backspace, Enter, Esc, Tab, Ctrl+ carta ). En GNU / Linux (dependiendo de la terminal exacta, tal vez?) Que también consigue Insert, Delete, Pg Up, Pg Dn, Home, Endy llaves ... pero entonces, hay cuestiones que separan estas teclas especiales de una .F nEsc

Advertencia: Al igual que con la mayoría de las respuestas (¿todas?) Aquí, las teclas de señal como Ctrl+ C, Ctrl+ Dy Ctrl+ Zse capturan y devuelven (como '\x03', '\x04'y '\x1a'respectivamente); Su programa puede ser difícil de abortar.


3
Funciona con Python 3 en Linux también. Mucho mejor que getch, porque readchar permite imprimir en stdout mientras espera la clave (a través de hilos o asincio).
wrobell

Probado en Win10 + Python 3.5: ERROR: root: 'en <cadena>' requiere una cadena como operando izquierdo, no bytes Traceback (última llamada reciente): Archivo ".. \ main.py", línea 184, en el resultado del contenedor = Func (* args, ** kwargs) Archivo "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", línea 41, en readch_eg print (readchar.readchar ()) Archivo "C: \ Users \ ipcjs \ AppData \ Local \ Programas \ Python \ Python35 \ lib \ site-packages \ readchar \ readchar_windows.py ", línea 14, en readchar mientras ch en '\ x00 \ xe0': TypeError: 'en <cadena>' requiere una cadena como operando izquierdo , no bytes
ipcjs

@ipcjs, informe de ese error a los mantenedores
Melih Yıldız '

1
Esta es la mejor respuesta. agregar una dependencia a la biblioteca VS C ++ solo por esta funcionalidad es una locura.
FistOfFury

18

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 publicación de blog .


No parece funcionar para mí: devuelve una cadena vacía inmediatamente después de llamar. En Linux con Python 3.6.
Marein

1
@Marein Si desea que se bloquee (espere la entrada), elimine el | os.O_NONBLOCK. De lo contrario, puede ponerlo en un bucle (es una buena idea dormir un poco en el bucle para evitar que gire).
Chris Gregg

En Python, es mejor usarlo while Trueentonces while 1.
Anónimo

10

Este código, basado aquí , elevará correctamente KeyboardInterrupt y EOFError si se presiona Ctrl+ Co Ctrl+ D.

Debería funcionar en Windows y Linux. Una versión de OS X está disponible desde 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()

7

La respuesta (actualmente) mejor clasificada (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 hay dos implementaciones que logran lo mismo pero con un código más legible.

Ambas implementaciones:

  1. funciona bien en Python 2 o Python 3
  2. trabajar en Windows, OSX y Linux
  3. lee solo un byte (es decir, no esperan una nueva línea)
  4. no dependa de ninguna biblioteca externa
  5. son independientes (no hay 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 importaciones repetidas y manejo de excepciones:

[EDITAR] Perdí una ventaja del código ActiveState. Si planea leer caracteres varias veces, ese código evita el costo (insignificante) de repetir la importación de Windows y el manejo de excepciones ImportError en sistemas similares a Unix. Si bien probablemente debería estar más preocupado por la legibilidad del código que por esa optimización insignificante, aquí hay una alternativa (es similar a la respuesta de Louis, pero getChar () es autónomo) 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 ejercita cualquiera de las versiones 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))

2
Me encontré con un problema con tty.setraw () al imprimir mensajes mientras esperaba simultáneamente una clave (multiproceso). En pocas palabras, descubrí que usar tty.setcbreak () te permite obtener un solo personaje sin romper todas las demás cosas normales. Larga historia en esta respuesta
TheDavidFactor

4

Este podría ser un caso de uso para un administrador de contexto. Dejando de lado las asignaciones para el sistema operativo Windows, esta es 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()

También podría volver self en __enter__y tener un readmétodo que devuelve sys.stdin.read(1), entonces se podría leer múltiples personajes en un contexto.
L3viathan

4

Intente usar esto: http://home.wlu.edu/~levys/software/kbhit.py No bloquea (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 puede usar el módulo getch de PyPi . Pero esto bloquearía el ciclo while


3

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)

1
@ThorSummoner: este código tiene varios problemas, así que no , no funcionará para aplicaciones de línea de comandos.
Martineau

Se ejecuta para una aplicación de línea de comandos, dado que el administrador de Windows se está ejecutando.
Davoud Taghawi-Nejad

No, no se ejecuta en un sistema operativo sin cabeza. Pero se ejecuta en una ventana de línea de comandos.
Davoud Taghawi-Nejad

3

Las respuestas aquí fueron informativas, sin embargo, también quería una forma de presionar las teclas de forma asincrónica y disparar las pulsaciones de teclas en eventos separados, todo de manera segura 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 cualquier otra persona. Lo almacené 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://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()

La idea es simplemente llamar keyPress.getKey(), lo que leerá una tecla del teclado y luego devolverla.

Si quieres algo más que eso, hice un KeyCaptureobjeto. Puedes 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 presiona 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 desee en ellos y no estropeará la funcionalidad de KeyCapturer ni retrasará los otros eventos.

get()devuelve una clave del mismo modo de bloqueo que antes. Ahora se necesita aquí porque las claves se están capturando a través del KeyCaptureobjeto ahora, por keyPress.getKey()lo que entraría en conflicto con ese comportamiento y ambos perderían algunas claves ya que solo se puede capturar una clave a la vez. Además, digamos que el usuario presiona 'a', luego 'b', usted llama get(), el usuario presiona 'c'. Esa get()llamada devolverá inmediatamente 'a', luego, si la vuelve a llamar, devolverá 'b', luego 'c'. Si lo vuelve a llamar, se bloqueará hasta que presione otra tecla. Esto asegura que no se pierda ninguna tecla, de forma bloqueada si lo desea. Entonces, de esta manera es un poco diferente que keyPress.getKey()antes

Si desea el comportamiento de getKey()atrás, get(lossy=True)es como get(), excepto que solo devuelve las teclas presionadas después de la llamada a get(). Entonces, en el ejemplo anterior, get()se bloquearía hasta que el usuario presione 'c', y luego, si lo vuelve a llamar, se bloqueará hasta que se presione otra tecla.

getAsync()Es un poco diferente. Está diseñado para algo que procesa mucho, luego vuelve ocasionalmente y comprueba qué teclas se presionaron. Por lo 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 más reciente. Tampoco se bloquea, lo que significa que si no se ha presionado ninguna tecla desde la última llamada getAsync(), []se devolverá un vacío .

Para comenzar realmente a capturar claves, debe llamar keys.startCapture()con su keysobjeto hecho anteriormente. startCaptureno bloquea, y simplemente inicia un hilo que solo 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 pierda ninguna tecla.

Si desea dejar de capturar claves, puede llamar keys.stopCapture()y dejará de capturar claves. Sin embargo, dado que capturar una clave es una operación de bloqueo, las claves de captura de subprocesos pueden capturar una clave más después de llamar stopCapture().

Para evitar esto, puede pasar un parámetro o parámetros opcionales a startCapture(functionName, args)una función que simplemente 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, dormir aquí nos hará perder teclas.

Sin embargo, si stopCapture()se llama en esta función, las capturas de teclas se detendrán de inmediato, sin intentar capturar más, y todas las get()llamadas se devolverán de inmediato, con Ninguno si aún no se ha presionado ninguna tecla.

Además, dado que get()y getAsync()almacenar todas las teclas presionadas anteriores (hasta que se recupere), puede llamar clearGetList()y clearAsyncList()olvidar las teclas pulsadas previamente.

Tenga en cuenta que get(), getAsync()y los eventos son independientes, por lo que si se presiona una tecla: 1. Una llamada get()que está en espera, con 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 obtención de claves, de modo que get()con pérdida desactivada, se devolverá la tecla más antigua presionada que aún no ha sido devuelta get(). 3. Todos los eventos se activarán con esa clave como su entrada 4. Esa clave se almacenará en la lista de getAsync()claves, donde se devolverá esa lista y se configurará como lista vacía en la próxima llamada agetAsync()

Si todo esto es demasiado, aquí hay un caso de uso de ejemplo:

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 a partir de la simple prueba que hice, pero felizmente recibiré comentarios de otros también si hay algo que me perdí.

Publiqué esto aquí también.


3

Un comentario en una de las otras respuestas mencionó el modo cbreak, que es importante para las implementaciones de Unix porque generalmente no desea que KeyboardErrorgetchar consuma ^ C ( ) (como lo hará cuando configura el terminal en modo sin procesar, como lo hace la mayoría de las otras respuestas).

Otro detalle importante es que si está buscando leer un carácter y no un byte , debe leer 4 bytes de la secuencia de entrada, ya que esa es la cantidad máxima de bytes que un solo carácter consistirá 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)

2

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, "."

Esa es una buena idea, pero no funciona en la línea de comando: pygame.error: video system not initialized
dirkjot

2

La receta de ActiveState parece contener un pequeño error para los sistemas "posix" que impide la Ctrl-Cinterrupción (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 ttyno es realmente necesario, termioses suficiente para manejarlo.

A continuación se muestra el código mejorado que funciona para mí ( Ctrl-Cse interrumpirá), con la getchefunción adicional que repite el carácter mientras 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://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)

Referencias


1

El cursespaquete 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 hacerse cargo de la pantalla para la salida, que puede no ser lo que desea. Este fragmento de código utiliza en su print()lugar declaraciones, que son utilizables, pero debe ser consciente de cómo las maldiciones cambian las terminaciones de línea asociadas 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!')

1

Si estoy haciendo algo complicado, usaré maldiciones para leer las claves. Pero muchas veces solo quiero un script Python 3 simple 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

0

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

0

Creo que esta es la solución más elegante.

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 úsalo en el código:

if getch() == chr(ESC_ASCII_VALUE):
    print("ESC!")

0

La respuesta aceptada no funcionó tan bien para mí (presionaría una tecla, no pasaría nada, luego presionaría otra tecla y funcionaría).

Después de aprender sobre el módulo de maldiciones , realmente parece ser el camino correcto. Y ahora está disponible para Windows a través de cursores de Windows (disponible a través de pip), por lo que puede programar de manera independiente de la plataforma. Aquí hay un ejemplo inspirado en este bonito tutorial en 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))

Guárdelo con una .pyextensión o ejecútelo curses.wrapper(getkey)en modo interactivo.


0

Respondido aquí: raw_input en python sin presionar enter

Usa este código

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

Referencia: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py


0

Si desea registrar solo una tecla, presione incluso si el usuario la presionó por más de una vez o siguió presionando la tecla por más tiempo. Para evitar obtener múltiples entradas presionadas, use el bucle while y páselo.

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)

0

si solo quieres sostener la pantalla para que puedas ver el resultado en el terminal solo escribe

input()

al final del código y mantendrá la pantalla


-1

El raw_input incorporado 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.")

66
raw_input está esperando la tecla enter
vac
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.