Python leu um único caractere do usuário


262

Existe uma maneira de ler um único caractere da entrada do usuário? Por exemplo, eles pressionam uma tecla no terminal e ela é retornada (mais ou menos como getch()). Eu sei que há uma função no Windows para isso, mas eu gostaria de algo que seja multiplataforma.


1
No Windows, tive o mesmo problema que nesta pergunta . A solução é substituir o msvcrt.getchpor msvcrt.getwch, como sugerido lá.
A. Roy

A solução é instalar o módulo getch "pip install getch". Para Python2, use o comando "pip2 install files.pythonhosted.org/packages/56/f7/… ". Esta solução também funciona no Termux (Android).
Petr Mach

Respostas:


190

Aqui está um link para um site que diz como você pode ler um único caractere no 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
O código parece curto o suficiente para você incluí-lo, mas +1 para encontrar uma boa resposta (multiplataforma) tão rapidamente.
7119 John Mulder

4
Ele lida bem com letras não latinas (por exemplo, cirílicas)? Estou tendo um problema com isso e não consigo descobrir, se é meu erro ou não.
Phlya

7
Não gosto de como a ImportErrorexceção é usada como algum tipo de declaração if; por que não chamar platform.system () para verificar o sistema operacional?
Seismoid 21/09

10
@Seismoid: Pedir perdão é geralmente considerado melhor, ver stackoverflow.com/questions/12265451/...
dirkjot

4
Não funciona no OS X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25, 'ioctl inadequado para o dispositivo')"
Exibir nome

80
sys.stdin.read(1)

lerá basicamente 1 byte do STDIN.

Se você deve usar o método que não espera, \nvocê pode usar este código como sugerido na resposta 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()

( extraído de http://code.activestate.com/recipes/134892/ )


34
Acho estranho que sys.stdin.read (1) aguarde um \ n, lol. Obrigado pelo envio, no entanto.
Evan Fosmark 04/02/09

3
Um caractere ou um byte? Isso não é o mesmo.
Chryss #

4
@Evan, isso é porque python está em modo tamponada linha por padrão
John La Rooy

3
@EvanFosmark: não é necessariamente que sys.stdin.read (1) aguarde \ n, é que o programa do terminal que decide quando enviar outros caracteres para o seu programa não os escreve até ver '\ n' - de que outra forma seria você pode pressionar backspace e corrigir o que está digitando? (a resposta séria para isso é: ensine o programa python a implementar o controle de linha, manter um buffer, processar backspaces, mas esse é um mundo diferente no qual você talvez não queira se interessar apenas "lendo um personagem" e pode fazer sua linha manipulação diferente de todos os outros programas em seu sistema).
Tony Delroy

2
@Seismoid EAFP
vaultah 27/09/15

70

A receita do ActiveState citada literalmente em duas respostas é exagerada. Pode-se resumir a isso:

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

Agradável. Mas isso também lerá o primeiro caractere do KeyboardInterrupt (Ctrl + C), e o código tem a possibilidade de sair com 0.
user3342816

51

Também vale a pena tentar é a biblioteca readchar , que é parcialmente baseada na receita do ActiveState mencionada em outras respostas.

Instalação:

pip install readchar

Uso:

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

Testado no Windows e Linux com Python 2.7.

No Windows, somente as chaves que são mapeados para letras ou códigos de controle ASCII são suportados ( Backspace, Enter, Esc, Tab, Ctrl+ letra ). No GNU / Linux (dependendo do terminal exata, talvez?) Você também terá Insert, Delete, Pg Up, Pg Dn, Home, Ende teclas ... mas, em seguida, há questões que separam estas teclas especiais de um .F nEsc

Advertência: Como com a maioria (?) Todas as respostas aqui, chaves de sinal como Ctrl+ C, Ctrl+ De Ctrl+ Zsão capturados e devolvidos (como '\x03', '\x04'e '\x1a'respectivamente); pode ser difícil abortar seu programa.


3
Também funciona com Python 3 no Linux. Muito melhor do que o getch, porque o readchar permite a impressão em stdout enquanto aguarda a tecla (via threads ou assíncrono).
wrobell

Testado no Win10 + Python 3.5: ERRO: root: 'in <string>' requer string como operando esquerdo, não bytes Traceback (última chamada mais recente): Arquivo ".. \ main.py", linha 184, no resultado do wrapper = func (* args, ** kwargs) Arquivo "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", linha 41, em readch_eg print (readchar.readchar ()) Arquivo "C: \ Users \ ipcjs \ AppData \ Local \ Programas \ Python \ Python35 \ lib \ pacotes de sites \ readchar \ readchar_windows.py ", linha 14, no readchar enquanto ch em '\ x00 \ xe0': TypeError: 'in <string>' requer string como operando esquerdo , não bytes
ipcjs 5/11/16

@ipcjs comunique esse bug aos mantenedores
Melih Yıldız '

1
Esta é a melhor resposta. adicionar uma dependência à biblioteca VS C ++ apenas para essa funcionalidade é uma loucura.
FistOfFury

18

Um 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

A partir deste post .


Parece não funcionar para mim - retorna uma string vazia imediatamente após a chamada. No Linux com Python 3.6.
Marein 06/04

1
@Marein Se você deseja bloquear (aguarde a entrada), remova o | os.O_NONBLOCK. Caso contrário, você pode colocá-lo em um loop (é uma boa idéia dormir um pouco no loop para não girar).
Chris Gregg

No Python, é melhor usar while Trueentão while 1.
Anônimo

10

Esse código, baseado aqui , aumentará corretamente KeyboardInterrupt e EOFError se Ctrl+ Cou Ctrl+ Dforem pressionados.

Deve funcionar no Windows e Linux. Uma versão do OS X está disponível na fonte 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

A resposta (atualmente) com a melhor classificação (com o código ActiveState) é muito complicada. Não vejo uma razão para usar classes quando uma mera função deve ser suficiente. Abaixo estão duas implementações que realizam a mesma coisa, mas com código mais legível.

Ambas as implementações:

  1. funciona muito bem em Python 2 ou Python 3
  2. trabalhar no Windows, OSX e Linux
  3. leia apenas um byte (ou seja, eles não esperam por uma nova linha)
  4. não dependa de bibliotecas externas
  5. são independentes (nenhum código fora da definição da função)

Versão 1: legível e simples

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

Versão 2: evite importações repetidas e manipulação de exceções:

[EDIT] Perdi uma vantagem do código do ActiveState. Se você planeja ler caracteres várias vezes, esse código evita o custo (desprezível) de repetir a importação do Windows e o tratamento de exceção ImportError em sistemas semelhantes ao Unix. Embora você provavelmente deva se preocupar mais com a legibilidade do código do que com a otimização insignificante, aqui está uma alternativa (é semelhante à resposta de Louis, mas getChar () é independente) que funciona da mesma forma que o código ActiveState e é mais legível:

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 exemplo que exercita uma das versões getChar () acima:

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
Encontrei um problema com o tty.setraw () ao imprimir mensagens enquanto esperava por uma chave (multithread). Para encurtar a história, descobri que usar tty.setcbreak () permite obter um único personagem sem quebrar todas as outras coisas normais. Longa história nesta resposta
TheDavidFactor

4

Este pode ser um caso de uso para um gerenciador de contexto. Deixando de lado os subsídios para o sistema operacional Windows, aqui está minha sugestão:

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

Você também pode voltar self em __enter__e ter um readmétodo que retorna sys.stdin.read(1), então você pode ler vários personagens em um contexto.
L3viathan #

4

Tente usar o seguinte: http://home.wlu.edu/~levys/software/kbhit.py É sem bloqueio (isso significa que você pode ter um loop while e detectar um pressionamento de tecla sem parar) e em várias plataformas.

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

Um exemplo para usar isso:

import kbhit

kb = kbhit.KBHit()

while(True): 
    print("Key not pressed") #Do something
    if kb.kbhit(): #If a key is pressed:
        k_in = kb.getch() #Detect what key was pressed
        print("You pressed ", k_in, "!") #Do something
kb.set_normal_term()

Ou você pode usar o módulo getch do PyPi . Mas isso bloquearia o loop while


3

É NÃO-BLOQUEIO, lê uma chave e a armazena em 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

no seu 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 tem vários problemas - portanto , não , ele não funcionará para aplicativos de linha de comando.
31515

É executado para um aplicativo de linha de comando, considerando que o gerenciador do Windows está sendo executado.
Davoud Taghawi-Nejad 31/08/2015

Não, ele não roda no sistema operacional sem cabeça. Mas ele é executado em uma janela de linha de comando.
Davoud Taghawi-Nejad 17/11/2015

3

As respostas aqui foram informativas, no entanto, eu também queria uma maneira de pressionar as teclas de forma assíncrona e acionar as teclas em eventos separados, tudo de maneira segura e em várias plataformas. PyGame também estava inchado demais para mim. Então fiz o seguinte (no Python 2.7, mas suspeito que seja facilmente portátil), que achei que compartilharia aqui caso fosse útil para qualquer outra pessoa. Guardei isso em um arquivo chamado 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()

A idéia é que você pode simplesmente ligar keyPress.getKey(), que lerá uma tecla do teclado e depois retornará.

Se você quer algo mais que isso, eu fiz um KeyCaptureobjeto. Você pode criar um via algo como keys = keyPress.KeyCapture().

Depois, há três coisas que você pode fazer:

addEvent(functionName)aceita qualquer função que aceite um parâmetro. Então, toda vez que uma tecla for pressionada, essa função será chamada com a string da tecla conforme for inserida. Eles são executados em um encadeamento separado, para que você possa bloquear tudo o que deseja e não atrapalhará a funcionalidade do KeyCapturer nem atrasará os outros eventos.

get()retorna uma chave da mesma maneira que o bloqueio anterior. Agora é necessário aqui, porque as chaves estão sendo capturadas por meio do KeyCaptureobjeto agora, portanto, keyPress.getKey()entrariam em conflito com esse comportamento e os dois perderiam algumas chaves, pois apenas uma chave pode ser capturada por vez. Diga também que o usuário pressiona 'a', depois 'b', você chama get(), o usuário pressiona 'c'. Essa get()chamada retornará imediatamente 'a'; se você ligar novamente, retornará 'b'; depois, 'c'. Se você ligar novamente, ele bloqueará até que outra tecla seja pressionada. Isso garante que você não perca nenhuma chave, de maneira bloqueadora, se desejar. Portanto, dessa maneira, é um pouco diferente do que keyPress.getKey()antes

Se você deseja o comportamento de getKey()voltar, get(lossy=True)é como get(), exceto que ele só retorna as teclas pressionadas após a chamada para get(). Portanto, no exemplo acima, get()seria bloqueado até o usuário pressionar 'c' e, se você chamá-lo novamente, ele bloqueará até que outra tecla seja pressionada.

getAsync()é um pouco diferente. Ele foi projetado para algo que processa muito e, ocasionalmente, volta e verifica quais teclas foram pressionadas. Assim, getAsync()retorna uma lista de todas as teclas pressionadas desde a última chamada para getAsync(), na ordem da tecla mais antiga para a tecla mais recente pressionada. Também não é bloqueado, o que significa que, se nenhuma tecla foi pressionada desde a última chamada getAsync(), um vazio []será retornado.

Para realmente começar a capturar as chaves, você precisa ligar keys.startCapture()com o seu keysobjeto feito acima. startCaptureé não-bloqueante e simplesmente inicia um segmento que registra apenas as teclas pressionadas e outro para processar essas teclas. Existem dois threads para garantir que o thread que registra pressionamentos de tecla não perca nenhuma tecla.

Se você deseja parar de capturar chaves, pode ligar keys.stopCapture()e ele irá parar de capturar chaves. No entanto, como capturar uma chave é uma operação de bloqueio, as chaves de captura de encadeamento podem capturar mais uma chave após a chamada stopCapture().

Para evitar isso, você pode passar um (s) parâmetro (s) opcional (s) para startCapture(functionName, args)uma função que faz algo como checa se uma tecla é igual a 'c' e sai. É importante que essa função faça muito pouco antes, por exemplo, dormir aqui nos fará perder as teclas.

No entanto, se stopCapture()for chamada nesta função, as capturas de teclas serão interrompidas imediatamente, sem tentar capturar mais, e todas as get()chamadas serão retornadas imediatamente, com None se nenhuma tecla tiver sido pressionada ainda.

Além disso, desde get()e getAsync()armazenar todas as teclas anteriores pressionadas (até recuperá-las), você pode ligar clearGetList()e clearAsyncList()esquecer as teclas pressionadas anteriormente.

Observe que get(), getAsync()e os eventos são independentes, portanto, se uma tecla for pressionada: 1. Uma chamada para get()a espera, com perdas ativadas, retornará a tecla. As outras chamadas em espera (se houver) continuarão em espera. 2. Essa tecla será armazenada na fila de teclas get, para que, get()com perdas desligadas, retorne a tecla mais antiga pressionada ainda não retornada get(). 3. Todos os eventos serão acionados com essa chave como entrada. 4. Essa chave será armazenada na lista de getAsync()chaves, em que a lis será retornada e configurada como lista vazia na próxima chamada paragetAsync()

Se tudo isso for demais, aqui está um exemplo de caso de uso:

import keyPress
import time
import threading

def KeyPressed(k, printLock):
    printLock.acquire()
    print "Event: " + k
    printLock.release()
    time.sleep(4)
    printLock.acquire()
    print "Event after delay: " + k
    printLock.release()

def GetKeyBlocking(keys, printLock):    
    while keys.capturing():
        keyReceived = keys.get()
        time.sleep(1)
        printLock.acquire()
        if not keyReceived is None:
            print "Block " + keyReceived
        else:
            print "Block None"
        printLock.release()

def GetKeyBlockingLossy(keys, printLock):   
    while keys.capturing():
        keyReceived = keys.get(lossy=True)
        time.sleep(1)
        printLock.acquire()
        if not keyReceived is None:
            print "Lossy: " + keyReceived
        else:
            print "Lossy: None"
        printLock.release()

def CheckToClose(k, (keys, printLock)):
    printLock.acquire()
    print "Close: " + k
    printLock.release()
    if k == "c":
        keys.stopCapture()

printLock = threading.Lock()

print "Press a key:"
print "You pressed: " + keyPress.getKey()
print ""

keys = keyPress.KeyCapture()

keys.addEvent(KeyPressed, printLock)



print "Starting capture"

keys.startCapture(CheckToClose, (keys, printLock))

getKeyBlockingThread = threading.Thread(target=GetKeyBlocking, args=(keys, printLock))
getKeyBlockingThread.daemon = True
getKeyBlockingThread.start()


getKeyBlockingThreadLossy = threading.Thread(target=GetKeyBlockingLossy, args=(keys, printLock))
getKeyBlockingThreadLossy.daemon = True
getKeyBlockingThreadLossy.start()

while keys.capturing():
    keysPressed = keys.getAsync()
    printLock.acquire()
    if keysPressed != []:
        print "Async: " + str(keysPressed)
    printLock.release()
    time.sleep(1)

print "done capturing"

Está funcionando bem para mim desde o teste simples que fiz, mas terei todo o prazer em receber feedback de outras pessoas se houver algo que eu perdi.

Eu postei isso aqui também.


3

Um comentário em uma das outras respostas mencionou o modo de quebra, o que é importante para implementações do Unix, porque geralmente você não deseja que ^ C ( KeyboardError) seja consumido pelo getchar (como será quando você definir o terminal no modo bruto, como feito por outras respostas).

Outro detalhe importante é que, se você deseja ler um caractere e não um byte , deve ler 4 bytes do fluxo de entrada, pois esse é o número máximo de bytes em que um único caractere será composto em UTF-8 (Python 3+ ) A leitura de apenas um byte produzirá resultados inesperados para caracteres de vários bytes, como setas do teclado.

Aqui está minha implementação alterada 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

Tente isso com 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, "."

Essa é uma idéia legal, mas ele não funciona na linha de comando: pygame.error: video system not initialized
dirkjot

2

A receita do ActiveState parece conter um pequeno bug para sistemas "posix" que impede a Ctrl-Cinterrupção (estou usando o Mac). Se eu colocar o seguinte código no meu script:

while(True):
    print(getch())

Nunca poderei terminar o script Ctrl-Ce preciso matar meu terminal para escapar.

Eu acredito que a seguinte linha é a causa, e também é muito brutal:

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

Além disso, o pacote ttynão é realmente necessário, termiosé suficiente para lidar com isso.

Abaixo está o código aprimorado que funciona para mim ( Ctrl-Cinterromperá), com a getchefunção extra que ecoa o char enquanto você digita:

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)

Referências:


1

O cursespacote em python pode ser usado para entrar no modo "bruto" para entrada de caracteres do terminal com apenas algumas instruções. O principal uso das maldições é assumir o controle da tela, o que pode não ser o que você deseja. Em print()vez disso, esse trecho de código usa instruções que são utilizáveis, mas você deve estar ciente de como as maldições alteram as terminações de linha associadas à saída.

#!/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 estou fazendo algo complicado, usarei maldições para ler as chaves. Mas muitas vezes eu só quero um script Python 3 simples que use a biblioteca padrão e possa ler teclas de seta, então faço isso:

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

Minha solução para python3, não depende de nenhum pacote de 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

Eu acredito que esta é uma das soluções mais elegantes.

import os

if os.name == 'nt':
    import msvcrt
    def getch():
        return msvcrt.getch().decode()
else:
    import sys, tty, termios
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    def getch():
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

e depois use-o no código:

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

0

A resposta aceita não funcionou tão bem para mim (eu segurei uma tecla, nada aconteceria, depois pressionei outra tecla e funcionaria).

Depois de aprender sobre o módulo de maldições , parece realmente o caminho certo a seguir. E agora está disponível para Windows através de cursores do Windows (disponível através do pip), para que você possa programar de maneira independente da plataforma. Aqui está um exemplo inspirado neste bom tutorial no 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))

Salve-o com uma .pyextensão ou execute curses.wrapper(getkey)no modo interativo.


0

Respondida aqui: raw_input em python sem pressionar enter

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

Referência: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py


0

Se você deseja registrar apenas uma única tecla, pressione mesmo que o usuário tenha pressionado por mais de uma vez ou tenha mantido a tecla pressionada por mais tempo. Para evitar receber várias entradas pressionadas, use o loop while e passe-o.

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 você quiser apenas segurar a tela para ver o resultado no terminal, basta escrever

input()

no final do código e ele manterá a tela


-1

O build_input_input deve ajudar.

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 está aguardando a tecla enter
vac
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.