Python ha letto un singolo personaggio dall'utente


262

Esiste un modo per leggere un singolo carattere dall'input dell'utente? Ad esempio, premono un tasto sul terminale e viene restituito (una specie di like getch()). So che esiste una funzione in Windows, ma mi piacerebbe qualcosa che sia multipiattaforma.


1
Su Windows ho riscontrato lo stesso problema di questa domanda . La soluzione è sostituire il msvcrt.getchcon msvcrt.getwch, come suggerito lì.
A. Roy,

La soluzione è installare il modulo getch "pip install getch". Per Python2 usa il comando "pip2 install files.pythonhosted.org/packages/56/f7/… ". Questa soluzione funziona anche in Termux (Android).
Petr Mach,

Risposte:


190

Ecco un link a un sito che dice come leggere un singolo personaggio in Windows, Linux e 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
il codice sembra abbastanza breve da poterlo semplicemente includere, ma +1 per trovare una buona risposta (multipiattaforma) così rapidamente.
John Mulder,

4
Gestisce bene le lettere non latine (es. Cirillico)? Sto avendo un problema con quello e non riesco a capire, se è il mio errore o no.
Phlya,

7
Non mi piace il modo in cui l' ImportErroreccezione viene utilizzata come una specie di istruzione if; perché non chiamare platform.system () per controllare il sistema operativo?
Seismoid,

10
@Seismoid: La richiesta di perdono è generalmente considerata migliore, vedi stackoverflow.com/questions/12265451/...
dirkjot

4
Non funziona su OS X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25, 'Ioctl inappropriato per dispositivo')"
Visualizza nome

80
sys.stdin.read(1)

leggerà sostanzialmente 1 byte da STDIN.

Se è necessario utilizzare il metodo che non attende per \nè possibile utilizzare questo codice come suggerito nella risposta precedente:

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

( tratto da http://code.activestate.com/recipes/134892/ )


34
Trovo strano che sys.stdin.read (1) aspetti a \ n, lol. Grazie per l'invio, comunque.
Evan Fosmark,

3
Un carattere o un byte? Non è lo stesso.
chryss,

4
@Evan, questo perché python è in modalità buffer di linea per impostazione predefinita
John La Rooy

3
@EvanFosmark: non è necessariamente che sys.stdin.read (1) aspetti \ n, è che il programma terminale che decide quando inviare altri caratteri al tuo programma non li scrive finché non vede '\ n' - altrimenti sei in grado di premere backspace e correggere ciò che stai digitando? (la risposta seria a questo è: insegnare al programma Python ad implementare il controllo della linea, mantenere un buffer, elaborare backspaces, ma è un mondo diverso in cui potresti non voler acquistare quando stai semplicemente "leggendo un personaggio" e potresti fare la tua linea gestione diversa da tutti gli altri programmi sul sistema.)
Tony Delroy,

2
@Seismoid EAFP
vaultah,

70

La ricetta di ActiveState citata alla lettera in due risposte è troppo ingegnerizzata. Può essere ridotto a questo:

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

Bello. Ma questo leggerà anche il primo carattere di KeyboardInterrupt (Ctrl + C) e il codice ha la possibilità di uscire con 0.
user3342816

51

Vale anche la pena provare il readchar libreria , che è in parte basata sulla ricetta ActiveState menzionata in altre risposte.

Installazione:

pip install readchar

Uso:

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

Testato su Windows e Linux con Python 2.7.

In Windows, sono supportati solo i tasti che mappano alle lettere o codici di controllo ASCII ( Backspace, Enter, Esc, Tab, Ctrl+ lettera ). Su GNU / Linux (a seconda del terminale esatto, forse?) Si ottiene anche Insert, Delete, Pg Up, Pg Dn, Home, Ende le chiavi ... ma poi, non c'è problemi che separano questi tasti speciali provenienti da un .F nEsc

Caveat: Come con la maggior parte (?) Tutte le risposte a qui, le chiavi di segnale come Ctrl+ C, Ctrl+ De Ctrl+ Zsono catturati e restituiti (come '\x03', '\x04'e '\x1a'rispettivamente); il tuo programma può essere difficile da interrompere.


3
Funziona con Python 3 anche su Linux. Molto meglio di getch, perché readchar consente di stampare su stdout mentre attende la chiave (tramite thread o asyncio).
Wrobell,

Testato su Win10 + Python 3.5 : ERRORE: root: 'in <stringa>' richiede una stringa come operando sinistro, non byte Traceback (ultima chiamata più recente): File ".. \ main.py", riga 184, in wrapper result = file func (* args, ** kwargs) "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", riga 41, in readch_eg print (readchar.readchar ()) File "C: \ Users \ ipcjs \ AppData \ Local \ Programmi \ Python \ Python35 \ lib \ site-pacchetti \ readchar \ readchar_windows.py ", riga 14, in readchar mentre ch in '\ x00 \ xe0': TypeError: 'in <string>' richiede stringa come operando sinistro , non byte
ipcjs,

@ipcjs, segnala questo bug ai manutentori
Melih Yıldız "

1
questa è la risposta migliore aggiungere una dipendenza alla libreria VS C ++ solo per questa funzionalità è pazzesco.
FistOfFury il

18

Un metodo 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

Da questo post sul blog .


Non sembra funzionare per me - restituisce una stringa vuota immediatamente dopo la chiamata. Su Linux con Python 3.6.
Marein,

1
@Marein Se si desidera bloccare (attendere l'immissione), rimuovere il | os.O_NONBLOCK. Altrimenti, puoi metterlo in un ciclo (buona idea dormire un po 'nel ciclo per evitare di girare).
Chris Gregg,

In Python, è meglio usarlo while Trueallora while 1.
Anonimo

10

Questo codice, basato qui , genererà correttamente KeyboardInterrupt ed EOFError se si preme Ctrl+ Co Ctrl+ D.

Dovrebbe funzionare su Windows e Linux. Una versione di OS X è disponibile dalla fonte originale.

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 risposta (attualmente) in primo piano (con il codice ActiveState) è eccessivamente complicata. Non vedo un motivo per usare le classi quando una semplice funzione dovrebbe essere sufficiente. Di seguito sono riportate due implementazioni che realizzano la stessa cosa ma con un codice più leggibile.

Entrambe queste implementazioni:

  1. funziona perfettamente in Python 2 o Python 3
  2. funziona su Windows, OSX e Linux
  3. leggere solo un byte (cioè, non aspettano una nuova riga)
  4. non dipendono da alcuna libreria esterna
  5. sono indipendenti (nessun codice al di fuori della definizione della funzione)

Versione 1: leggibile e semplice

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

Versione 2: evitare importazioni ripetute e gestione delle eccezioni:

[EDIT] Ho perso un vantaggio del codice ActiveState. Se si prevede di leggere più caratteri, quel codice evita il costo (trascurabile) di ripetere l'importazione di Windows e la gestione delle eccezioni ImportError su sistemi simili a Unix. Mentre probabilmente dovresti essere più preoccupato per la leggibilità del codice che per quella trascurabile ottimizzazione, ecco un'alternativa (è simile alla risposta di Louis, ma getChar () è autonomo) che funziona allo stesso modo del codice ActiveState ed è più leggibile:

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

Codice di esempio che esercita una delle versioni getChar () sopra:

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
Ho riscontrato un problema con tty.setraw () durante la stampa di messaggi mentre aspettavo contemporaneamente una chiave (multi-thread). Per farla breve, ho scoperto che l'uso di tty.setcbreak () ti consente di ottenere un singolo personaggio senza rompere tutte le altre cose normali. Lunga storia in questa risposta
TheDavidFactor

4

Questo potrebbe essere un caso d'uso per un gestore di contesto. Lasciando da parte le quote per il sistema operativo Windows, ecco il mio suggerimento:

#!/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()

Si potrebbe anche tornare self in __enter__e hanno un readmetodo che restituisce sys.stdin.read(1), allora si potevano leggere i caratteri multipli in un unico contesto.
L3viathan,

4

Prova a usare questo: http://home.wlu.edu/~levys/software/kbhit.py È non bloccante (ciò significa che puoi avere un ciclo while e rilevare una pressione del tasto senza fermarlo) e multipiattaforma.

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 esempio per usare questo:

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

Oppure potresti usare il modulo getch di PyPi . Ma questo bloccherebbe il ciclo while


3

Questo è NON BLOCCANTE, legge una chiave e la memorizza in 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

nel tuo programma

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)

1
@ThorSummoner: questo codice presenta numerosi problemi, quindi no , non funzionerà per le applicazioni da riga di comando.
martineau,

Funziona per un'applicazione da riga di comando, dato che il gestore di windows è in esecuzione.
Davoud Taghawi-Nejad,

No, non funziona nel sistema operativo senza testa. Ma viene eseguito in una finestra della riga di comando.
Davoud Taghawi-Nejad,

3

Le risposte qui sono state istruttive, tuttavia volevo anche un modo per ottenere le pressioni dei tasti in modo asincrono e spegnere le pressioni dei tasti in eventi separati, il tutto in modo sicuro e multipiattaforma. Anche PyGame era troppo gonfio per me. Quindi ho fatto quanto segue (in Python 2.7 ma sospetto che sia facilmente trasportabile), che ho pensato di condividere qui nel caso fosse utile per chiunque altro. L'ho archiviato in un file chiamato 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'idea è che puoi semplicemente chiamare keyPress.getKey(), che leggerà un tasto dalla tastiera, quindi lo restituirà.

Se vuoi qualcosa di più, ho creato un KeyCaptureoggetto. Puoi crearne uno tramite qualcosa di similekeys = keyPress.KeyCapture() .

Quindi ci sono tre cose che puoi fare:

addEvent(functionName)accetta qualsiasi funzione che accetta un parametro. Quindi ogni volta che viene premuto un tasto, questa funzione viene chiamata con la stringa di quel tasto mentre viene immessa. Questi vengono eseguiti in un thread separato, quindi puoi bloccare tutto ciò che vuoi in loro e non rovinerà la funzionalità di KeyCapturer né ritarderà gli altri eventi.

get()restituisce una chiave nello stesso modo di blocco di prima. Ora è necessario qui perché le chiavi vengono acquisite tramite l' KeyCaptureoggetto ora, quindi keyPress.getKey()sarebbe in conflitto con quel comportamento ed entrambi perderebbero alcune chiavi poiché è possibile acquisire solo una chiave alla volta. Inoltre, dire che l'utente preme 'a', quindi 'b', si chiama get(), l'utente preme 'c'. Quella get()chiamata restituirà immediatamente "a", quindi se la chiami di nuovo restituirà "b", quindi "c". Se lo chiamate di nuovo, si bloccherà fino a quando non viene premuto un altro tasto. Questo ti assicura di non perdere nessuna chiave, se lo desideri in modo bloccante. Quindi in questo modo è un po 'diverso rispetto keyPress.getKey()a prima

Se vuoi il comportamento di getKey()back,get(lossy=True) è come get(), tranne per il fatto che restituisce solo i tasti premuti dopo la chiamata a get(). Quindi nell'esempio sopra, get()si bloccherebbe fino a quando l'utente preme 'c', e quindi se lo si chiama di nuovo si bloccherà fino a quando non viene premuto un altro tasto.

getAsync()è un po 'diverso. È progettato per qualcosa che fa molta elaborazione, poi occasionalmente ritorna e controlla quali tasti sono stati premuti. Quindi getAsync()restituisce un elenco di tutti i tasti premuti dall'ultima chiamata a getAsync(), in ordine dal tasto più vecchio premuto al tasto più recente premuto. Inoltre non si blocca, il che significa che se non è stato premuto alcun tasto dall'ultima chiamata a getAsync(), []verrà restituito uno spazio vuoto .

Per avviare effettivamente l'acquisizione delle chiavi, è necessario chiamare keys.startCapture()con l' keysoggetto creato in precedenza. startCaptureè non bloccante e avvia semplicemente un thread che registra solo le pressioni dei tasti e un altro thread per elaborare tali pressioni dei tasti. Esistono due thread per garantire che il thread che registra la pressione dei tasti non perda alcun tasto.

Se si desidera interrompere l'acquisizione delle chiavi, è possibile chiamare keys.stopCapture() e interromperà l'acquisizione delle chiavi. Tuttavia, poiché acquisire una chiave è un'operazione di blocco, le chiavi di acquisizione thread potrebbero acquisire un'altra chiave dopo aver chiamato stopCapture().

Per evitare ciò, è possibile passare un parametro facoltativo in startCapture(functionName, args) una funzione che fa semplicemente qualcosa come verificare se un tasto è uguale a 'c' e quindi esce. È importante che questa funzione faccia ben poco prima, ad esempio, un sonno qui ci farà perdere i tasti.

Tuttavia, se stopCapture()viene chiamato in questa funzione, le acquisizioni dei tasti verranno immediatamente interrotte, senza tentare di acquisire più, e tutto il restoget() chiamate verranno immediatamente restituite, con Nessuno se non è stato ancora premuto alcun tasto.

Inoltre, poiché get()e getAsync()memorizzare tutti i tasti precedenti premuti (fino a quando non li si recupera), è possibile chiamare clearGetList()eclearAsyncList() dimenticare i tasti precedentemente premuti.

Si noti che get(), getAsync()e gli eventi sono indipendenti, quindi se si preme un tasto: 1. Una chiamata in get()attesa, con perdita in corso, restituirà quel tasto. Le altre chiamate in attesa (se presenti) continueranno ad attendere. 2. Quella chiave verrà memorizzata nella coda di get keys, in modo che get()con lossy off restituisca la chiave più vecchia premuta non get()ancora restituita . 3. Tutti gli eventi verranno attivati ​​con quel tasto come input 4. Tale tasto verrà memorizzato nell'elenco dei getAsync()tasti, dove verrà restituito tale elenco e impostato su un elenco vuoto alla chiamata successiva agetAsync()

Se tutto ciò è troppo, ecco un esempio di utilizzo:

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"

Funziona bene per me dal semplice test che ho fatto, ma accetterò volentieri anche il feedback degli altri se c'è qualcosa che mi è sfuggito.

Ho pubblicato anche qui .


3

Un commento in una delle altre risposte menzionava la modalità cbreak, che è importante per le implementazioni di Unix perché generalmente non vuoi ^ C (KeyboardError ) venga consumato da getchar (come farà quando imposti il ​​terminale in modalità raw, come fatto da la maggior parte delle altre risposte).

Un altro dettaglio importante è che se stai cercando di leggere un carattere e non un byte , dovresti leggere 4 byte dal flusso di input, poiché questo è il numero massimo di byte che un singolo carattere sarà costituito in UTF-8 (Python 3+ ). La lettura di un solo byte produrrà risultati imprevisti per caratteri multi-byte come le frecce della tastiera.

Ecco la mia implementazione modificata per 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

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

È un'idea pygame.error: video system not initialized
chiara

2

La ricetta di ActiveState sembra contenere un piccolo bug per i sistemi "posix" che impedisce l' Ctrl-Cinterruzione (sto usando Mac). Se inserisco il seguente codice nel mio script:

while(True):
    print(getch())

Non sarò mai in grado di terminare la sceneggiatura Ctrl-Ce devo scappare dal mio terminale.

Credo che la seguente linea sia la causa, ed è anche troppo brutale:

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

A parte questo, il pacchetto ttynon è davvero necessario, termiosè sufficiente per gestirlo.

Di seguito è riportato il codice migliorato che funziona per me ( Ctrl-Cinterromperà), con la getchefunzione aggiuntiva che fa eco al carattere durante la digitazione:

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)

Riferimenti:


1

Il cursespacchetto in python può essere usato per entrare in modalità "raw" per l'immissione di caratteri dal terminale con poche istruzioni. L'uso principale delle maledizioni è quello di assumere lo schermo per l'output, che potrebbe non essere quello che desideri. Questo frammento di codice utilizza print()invece le istruzioni, che sono utilizzabili, ma è necessario essere consapevoli di come le maledizioni cambiano le terminazioni di linea associate all'output.

#!/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

Se sto facendo qualcosa di complicato, userò maledizioni per leggere le chiavi. Ma molte volte voglio solo un semplice script Python 3 che utilizza la libreria standard e può leggere i tasti freccia, quindi faccio questo:

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

La mia soluzione per python3, non dipende da alcun pacchetto 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

Credo che questa sia la soluzione più 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

e poi usalo nel codice:

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

0

La risposta accettata non ha funzionato così bene per me (avrei tenuto un tasto, non sarebbe successo nulla, quindi avrei premuto un altro tasto e avrebbe funzionato).

Dopo aver appreso del modulo maledizioni , sembra davvero la strada giusta da percorrere. Ed è ora disponibile per Windows tramite i cursori di Windows (disponibile tramite pip), in modo da poter programmare in modo indipendente dalla piattaforma. Ecco un esempio ispirato a questo simpatico tutorial su 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))

Salvalo con .pyun'estensione o eseguilo curses.wrapper(getkey)in modalità interattiva.


0

Risposto qui: raw_input in Python senza premere Invio

Usa questo codice

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

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


0

Se si desidera registrare un solo tasto, premere anche se l'utente lo ha premuto per più di una volta o se ha continuato a premere il tasto più a lungo. Per evitare di ottenere più ingressi premuti, utilizzare il ciclo while e passarlo.

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

se vuoi solo tenere lo schermo in modo da poter vedere il risultato sul terminale scrivi

input()

alla fine del codice e manterrà lo schermo


-1

Il raw_input integrato dovrebbe aiutare.

for i in range(3):
    print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")

6
raw_input è in attesa della chiave di invio
vac
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.