Python odczytał pojedynczy znak od użytkownika


260

Czy istnieje sposób na odczytanie jednego znaku z danych wprowadzonych przez użytkownika? Na przykład naciskają jeden klawisz na terminalu i jest on zwracany (podobnie jak getch()). Wiem, że jest w tym funkcja w systemie Windows, ale chciałbym coś, co jest wieloplatformowe.


1
W systemie Windows napotkałem ten sam problem jak w tym pytaniu . Rozwiązaniem jest zastąpienie msvcrt.getchz msvcrt.getwch, jak sugeruje tam.
A. Roy

Rozwiązaniem jest moduł getch install „pip install getch”. W przypadku Python2 użyj polecenia „pip2 install files.pythonhosted.org/packages/56/f7/… ”. To rozwiązanie działa również w systemie Termux (Android).
Petr Mach

Odpowiedzi:


189

Oto link do strony, która mówi, jak odczytać pojedynczy znak w systemie Windows, Linux i 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
kod wydaje się na tyle krótki, że można go po prostu dołączyć, ale +1 za szybkie znalezienie dobrej (międzyplatformowej) odpowiedzi.
John Mulder

4
Czy dobrze obsługuje litery inne niż łacińskie (np. Cyrylica)? Mam z tym problem i nie mogę zrozumieć, czy to mój błąd, czy nie.
Phlya

7
Nie podoba mi się sposób, w jaki ImportErrorużywany jest wyjątek, jak jakiś rodzaj instrukcji if; dlaczego nie wywołać platformy.system (), aby sprawdzić system operacyjny?
Seismoid

10
@Seismoid: Proszenie o wybaczenie jest ogólnie uważane za lepsze, patrz stackoverflow.com/questions/12265451/…
dirkjot

4
Nie działa w systemie OS X: „old_settings = termios.tcgetattr (fd)” „termios.error: (25,„ Nieodpowiednie ioctl dla urządzenia ”)”
Nazwa wyświetlana

79
sys.stdin.read(1)

w zasadzie odczyta 1 bajt ze STDIN.

Jeśli musisz użyć metody, która nie czeka na \n, możesz użyć tego kodu, jak sugerowano w poprzedniej odpowiedzi:

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

( pochodzi z http://code.activestate.com/recipes/134892/ )


33
Wydaje mi się dziwne, że sys.stdin.read (1) czeka na \ n, lol. Ale dziękuję za przesłanie.
Evan Fosmark

3
Jeden znak czy jeden bajt? To nie to samo.
chryss

4
@Evan, to dlatego, że python jest domyślnie w trybie buforowanym wierszowo
John La Rooy,

3
@EvanFosmark: niekoniecznie sys.stdin.read (1) czeka na \ n, to dlatego, że program terminalowy decydujący, kiedy wysłać inne znaki do twojego programu, nie zapisuje ich, dopóki nie zobaczy „\ n” - jak inaczej możesz nacisnąć klawisz Backspace i poprawić wpisywane słowo? (poważna odpowiedź na to pytanie - naucz program pythonowy implementacji kontroli linii, utrzymywania bufora, przetwarzania backspace, ale to jest inny świat, w którym nie chcesz kupować, kiedy „czytasz znak” i może stworzyć twoją linię obsługa inna niż wszystkie inne programy w twoim systemie.)
Tony Delroy

2
@Seismoid EAFP
vaultah

70

Przepis przepisu ActiveState cytowany dosłownie w dwóch odpowiedziach jest przeprojektowany. Można to sprowadzić do tego:

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

Miły. Ale to również przeczyta pierwszy znak KeyboardInterrupt (Ctrl + C), a kod ma możliwość wyjścia z 0.
user3342816

51

Warto również wypróbować bibliotekę readchar , która częściowo opiera się na recepturze ActiveState wspomnianej w innych odpowiedziach.

Instalacja:

pip install readchar

Stosowanie:

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

Testowane w systemach Windows i Linux za pomocą Python 2.7.

W systemie Windows, tylko klawisze która mapa do liter lub kodów ASCII są obsługiwane ( Backspace, Enter, Esc, Tab, Ctrl+ litera ). Na GNU / Linux (w zależności od dokładnego terminalu, może?) Można również uzyskać Insert, Delete, Pg Up, Pg Dn, Home, Endi klucze ... ale potem, nie ma problemów oddzielające te specjalne klucze od .F nEsc

Zastrzeżenie: Podobnie jak w przypadku większości (wszystkich?) Odpowiedzi w tutaj, klawiszy nawigacyjnych, takich jak Ctrl+ C, Ctrl+ Di Ctrl+ Zsą wychwytywane i wrócił (a '\x03', '\x04'i '\x1a', odpowiednio); twój program może być trudny do przerwania.


3
Działa również z Python 3 w systemie Linux. Znacznie lepiej niż getch, ponieważ readchar pozwala drukować na standardowe wyjście podczas oczekiwania na klucz (przez wątki lub asyncio).
wrobell

Testowany na Win10 + Python 3.5 R BŁĄD: root: „w <łańcuch>> wymaga ciągu jako lewego operandu, a nie bajtów Traceback (ostatnie ostatnie wywołanie): Plik„ .. \ main.py ”, wiersz 184, w wyniku opakowania = func (* args, ** kwargs) Plik „C: \ GitHub \ Python-Demo \ demo \ day_hello.py”, wiersz 41, w druku readch_eg (readchar.readchar ()) Plik „C: \ Users \ ipcjs \ AppData \ Local \ Programs \ Python \ Python35 \ lib \ site-packages \ readchar \ readchar_windows.py ", wiersz 14, w readchar, podczas gdy ch w '\ x00 \ xe0': TypeError: 'w <łańcuch> wymaga łańcucha jako lewego operandu , nie bajty
ipcjs

@ipcjs zgłoś ten błąd do opiekunów
Melih Yıldız '

1
to najlepsza odpowiedź. dodawanie zależności do biblioteki VS C ++ tylko dla tej funkcjonalności jest szalone.
FistOfFury,

17

Alternatywna metoda:

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

Z tego postu na blogu .


Wydaje się, że nie działa dla mnie - zwraca pusty ciąg znaków natychmiast po wywołaniu. W systemie Linux z Python 3.6.
Marein

1
@Marein Jeśli chcesz, aby się blokował (poczekaj na dane wejściowe), usuń | os.O_NONBLOCK. W przeciwnym razie możesz umieścić go w pętli (dobrym pomysłem jest spanie przez chwilę w pętli, aby nie wirować).
Chris Gregg

W Pythonie lepiej jest while Truewtedy użyć while 1.
Anonimowy

10

Ten kod, oparty tutaj , poprawnie podniesie KeyboardInterrupt i EOFError, jeśli naciśniesz Ctrl+ Club Ctrl+ D.

Powinien działać w systemach Windows i Linux. Wersja OS X jest dostępna z oryginalnego źródła.

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

(Obecnie) najwyższa odpowiedź (z kodem ActiveState) jest zbyt skomplikowana. Nie widzę powodu, aby używać klas, gdy wystarczy zwykła funkcja. Poniżej znajdują się dwie implementacje, które osiągają to samo, ale z bardziej czytelnym kodem.

Oba te wdrożenia:

  1. działają dobrze w Python 2 lub Python 3
  2. pracować w systemie Windows, OSX i Linux
  3. przeczytaj tylko jeden bajt (tzn. nie czekają na nowy wiersz)
  4. nie zależą od żadnych bibliotek zewnętrznych
  5. są samodzielne (bez kodu poza definicją funkcji)

Wersja 1: czytelna i prosta

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

Wersja 2: unikaj powtarzalnego importu i obsługi wyjątków:

[EDYCJA] Straciłem jedną zaletę kodu ActiveState. Jeśli planujesz wielokrotnie odczytywać znaki, kod ten pozwala uniknąć (nieistotnego) kosztu powtórzenia importu Windows i obsługi wyjątku ImportError w systemach uniksopodobnych. Podczas gdy prawdopodobnie powinieneś bardziej martwić się o czytelność kodu niż tę pomijalną optymalizację, oto alternatywa (jest podobna do odpowiedzi Louisa, ale getChar () jest samodzielna), która działa tak samo jak kod ActiveState i jest bardziej czytelna:

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

Przykładowy kod wykonujący jedną z powyższych wersji getChar ():

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
Wystąpił problem z tty.setraw () podczas drukowania wiadomości podczas jednoczesnego oczekiwania na klucz (wielowątkowy). Krótko mówiąc, odkryłem, że użycie tty.setcbreak () pozwala uzyskać jedną postać bez zepsucia wszystkich innych normalnych rzeczy. Długa historia w tej odpowiedzi
TheDavidFactor

4

Może to być przypadek użycia menedżera kontekstu. Pomijając limity dla systemu operacyjnego Windows, oto moja sugestia:

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

Można również zwrócić self się __enter__i mieć readmetodę, która powraca sys.stdin.read(1), a następnie można odczytać wiele znaków w jednym kontekście.
L3viathan

4

Spróbuj użyć tego: http://home.wlu.edu/~levys/software/kbhit.py To nie jest blokowanie (oznacza, że ​​możesz mieć pętlę while i wykryć naciśnięcie klawisza bez zatrzymywania go) i międzyplatformowe.

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

Przykład użycia tego:

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

Lub możesz użyć modułu getch z PyPi . Ale to zablokowałoby pętlę while


3

To NIE BLOKUJE, odczytuje klucz i przechowuje go w 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

w twoim programie

keypress = Keypress()

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

1
@ThorSummoner: Ten kod ma wiele problemów - więc nie , nie będzie działać w aplikacjach wiersza poleceń.
martineau

Działa dla aplikacji wiersza poleceń, biorąc pod uwagę, że uruchomiony jest menedżer systemu Windows.
Davoud Taghawi-Nejad

Nie, nie działa w bezgłowym systemie operacyjnym. Ale działa w oknie wiersza poleceń.
Davoud Taghawi-Nejad,

3

Odpowiedzi tutaj miały charakter informacyjny, ale chciałem również sposób na asynchroniczne uzyskiwanie naciśnięć klawiszy i uruchamianie naciśnięć klawiszy w oddzielnych zdarzeniach, wszystko w sposób bezpieczny dla wątków i na różnych platformach. PyGame też było dla mnie zbyt rozdęte. Więc zrobiłem następujące (w Pythonie 2.7, ale podejrzewam, że jest łatwo przenośne), które, jak sądzę, podzielę się tutaj na wypadek, gdyby był użyteczny dla kogokolwiek innego. Przechowałem to w pliku o nazwie 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()

Chodzi o to, że możesz po prostu zadzwonić keyPress.getKey(), który przeczyta klawisz z klawiatury, a następnie zwróci go.

Jeśli chcesz czegoś więcej, stworzyłem KeyCaptureobiekt. Możesz go utworzyć za pomocą czegoś takiego keys = keyPress.KeyCapture().

Następnie możesz zrobić trzy rzeczy:

addEvent(functionName)przyjmuje dowolną funkcję, która przyjmuje jeden parametr. Następnie po każdym naciśnięciu klawisza funkcja ta będzie wywoływana za pomocą łańcucha tego klawisza podczas wprowadzania. Są one uruchamiane w osobnym wątku, więc możesz zablokować w nich wszystko, co chcesz, i nie zakłóci to działania KeyCapturer ani nie opóźni innych zdarzeń.

get()zwraca klucz w taki sam sposób blokowania jak poprzednio. Jest on teraz potrzebny tutaj, ponieważ klucze są teraz przechwytywane przez KeyCaptureobiekt, więc keyPress.getKey()byłoby to sprzeczne z tym zachowaniem i oba z nich pominęłyby niektóre klucze, ponieważ tylko jeden klucz może zostać przechwycony na raz. Powiedz też, że użytkownik naciska „a”, a następnie „b”, dzwonisz get(), użytkownik naciska „c”. To get()połączenie natychmiast zwróci „a”, a następnie, jeśli zadzwonisz ponownie, zwróci „b”, a następnie „c”. Ponowne wywołanie spowoduje zablokowanie do momentu naciśnięcia innego klawisza. Zapewnia to, że nie przegapisz żadnych kluczy, w razie potrzeby blokujących. W ten sposób jest trochę inaczej niż keyPress.getKey()wcześniej

Jeśli chcesz zachować zachowanie getKey()wstecz, get(lossy=True)jest podobne get(), z tym wyjątkiem, że zwraca tylko klawisze naciśnięte po wywołaniu get(). Tak więc w powyższym przykładzie get()blokuje się, dopóki użytkownik nie naciśnie „c”, a następnie, jeśli wywołasz go ponownie, będzie blokować, dopóki nie zostanie naciśnięty inny klawisz.

getAsync()jest trochę inny. Jest przeznaczony do czegoś, co wymaga dużego przetwarzania, a następnie czasami wraca i sprawdza, które klawisze zostały naciśnięte. W ten sposób getAsync()zwraca listę wszystkich klawiszy naciśniętych od ostatniego połączenia getAsync(), w kolejności od najstarszego klawisza do ostatniego klawisza. Nie blokuje również, co oznacza, że ​​jeśli od ostatniego połączenia nie zostanie naciśnięty żaden klawisz getAsync(), pusty []zostanie zwrócony.

Aby zacząć przechwytywanie klawiszy, trzeba zadzwonić keys.startCapture()z Twój keysobiekt wykonany powyżej. startCapturejest nieblokujący i po prostu uruchamia jeden wątek, który rejestruje naciśnięcia klawiszy, a drugi wątek do przetwarzania tych naciśnięć klawiszy. Istnieją dwa wątki, które gwarantują, że wątek, który rejestruje naciśnięcia klawiszy, nie omija żadnych klawiszy.

Jeśli chcesz zatrzymać przechwytywanie kluczy, możesz zadzwonić, keys.stopCapture()a to zatrzyma przechwytywanie kluczy. Ponieważ jednak przechwytywanie klucza jest operacją blokującą, klucze przechwytujące wątek mogą przechwytywać jeszcze jeden klucz po wywołaniu stopCapture().

Aby temu zapobiec, możesz przekazać opcjonalne parametry do startCapture(functionName, args)funkcji, która po prostu sprawdza, czy klucz jest równy „c”, a następnie kończy działanie. Ważne jest, aby ta funkcja działała bardzo niewiele, na przykład sen tutaj spowoduje, że stracimy klucze.

Jeśli jednak stopCapture()zostanie wywołane w tej funkcji, przechwytywanie klawiszy zostanie natychmiast zatrzymane, bez próby przechwycenia, i że wszystkie get()połączenia zostaną natychmiast zwrócone, z Brak, jeśli żaden klawisz nie został jeszcze naciśnięty.

Ponadto, ponieważ get()i getAsync()przechowuj wszystkie poprzednie naciśnięte klawisze (dopóki ich nie odzyskasz), możesz zadzwonić clearGetList()i clearAsyncList()zapomnieć wcześniej naciśnięte klawisze.

Należy zauważyć, że get(), getAsync()i zdarzenia są niezależne, więc jeśli zostanie naciśnięty klawisz: 1. Jeden telefon na get()który czeka, z stratny na powrócą ten klucz. Inne połączenia oczekujące (jeśli istnieją) będą nadal czekać. 2. Ten klucz zostanie zapisany w kolejce kluczy get, dzięki czemu get()przy stratnym wyłączeniu zwróci najstarszy klawisz, który nie został get()jeszcze zwrócony . 3. Wszystkie zdarzenia będą uruchamiane z tym kluczem jako ich wejściem 4. Ten klucz zostanie zapisany na liście getAsync()kluczy, gdzie lis zostanie zwrócony i ustawiony na pustą listę przy następnym wywołaniugetAsync()

Jeśli to wszystko za dużo, oto przykładowy przypadek użycia:

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"

Działa dla mnie dobrze po prostym teście, który przeprowadziłem, ale chętnie przyjmę opinie innych, jeśli coś mi umknęło.

Też zamieściłem to tutaj .


3

Komentarz w jednej z pozostałych odpowiedzi wspomniał o trybie szyfrowania, który jest ważny dla implementacji Uniksa, ponieważ generalnie nie chcesz, KeyboardErroraby getchar zużywał ^ C ( ) (tak jak wtedy, gdy ustawisz terminal w trybie surowym, tak jak robi to większość innych odpowiedzi).

Innym ważnym szczegółem jest to, że jeśli chcesz odczytać jeden znak, a nie jeden bajt , powinieneś odczytać 4 bajty ze strumienia wejściowego, ponieważ jest to maksymalna liczba bajtów, z których pojedynczy znak będzie się składał w UTF-8 (Python 3+ ). Odczyt tylko jednego bajtu spowoduje nieoczekiwane wyniki dla znaków wielobajtowych, takich jak strzałki na klawiaturze.

Oto moja zmieniona implementacja dla Uniksa:

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

Wypróbuj to z 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, "."

To fajny pomysł, ale nie działa w wierszu poleceń: pygame.error: video system not initialized
dirkjot

2

Recepta ActiveState zawiera mały błąd w systemach „posix”, który zapobiega Ctrl-Czakłóceniom (używam Maca). Jeśli wstawię następujący kod do mojego skryptu:

while(True):
    print(getch())

Nigdy nie będę w stanie zakończyć skryptu Ctrl-Ci muszę zabić terminal, aby uciec.

Uważam, że następująca linijka jest przyczyną, a także zbyt brutalna:

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

Poza tym pakiet ttynie jest tak naprawdę potrzebny, termioswystarczy go obsłużyć.

Poniżej znajduje się ulepszony kod, który działa dla mnie ( Ctrl-Cprzerwie), z dodatkową getchefunkcją, która echa znaku podczas pisania:

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)

Bibliografia:


1

cursesPakiet w Pythonie można wykorzystać, aby wejść do trybu „surowego” dla wprowadzania znaków z terminala z zaledwie kilku stwierdzeń. Głównym zastosowaniem Curses jest przejęcie ekranu w celu uzyskania wyników, co może nie być tym, czego chcesz. Ten fragment kodu używa print()zamiast tego instrukcji, które są użyteczne, ale musisz zdawać sobie sprawę z tego, jak curses zmienia zakończenia linii dołączane do danych wyjściowych.

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

Jeśli robię coś skomplikowanego, używam przekleństw do czytania kluczy. Ale często potrzebuję prostego skryptu Python 3, który korzysta ze standardowej biblioteki i potrafi czytać klawisze strzałek, więc robię to:

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

Moje rozwiązanie dla python3, niezależne od pakietów 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

Uważam, że jest to jedno z najbardziej eleganckich rozwiązań.

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

a następnie użyj go w kodzie:

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

0

Przyjęta odpowiedź nie była dla mnie tak dobra (przytrzymałbym klucz, nic by się nie wydarzyło, a następnie nacisnąłem inny klawisz i zadziałałoby).

Po zapoznaniu się z modułem klątw , wydaje się, że to właściwa droga. I jest teraz dostępny dla Windows za pomocą kursorów Windows (dostępny przez pip), więc możesz programować w sposób agnostyczny na platformie. Oto przykład zainspirowany tym ładnym samouczkiem na 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))

Zapisz go z .pyrozszerzeniem lub uruchom curses.wrapper(getkey)w trybie interaktywnym.


0

Odpowiedzi tutaj: raw_input w pythonie bez naciskania enter

Użyj tego kodu

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

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


0

Jeśli chcesz zarejestrować tylko jeden pojedynczy klawisz, naciśnij nawet jeśli użytkownik naciskał go więcej niż jeden raz lub naciskał klawisz dłużej. Aby uniknąć wielokrotnego naciskania wejść, użyj pętli while i przekaż ją.

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

jeśli chcesz tylko przytrzymać ekran, aby zobaczyć wynik na terminalu, po prostu napisz

input()

na końcu kodu i przytrzyma ekran


-1

Wbudowane raw_input powinno pomóc.

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 czeka na klawisz Enter
vac
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.