Python membaca satu karakter dari pengguna

262

Apakah ada cara membaca satu karakter tunggal dari input pengguna? Misalnya, mereka menekan satu tombol di terminal dan dikembalikan (semacam suka getch()). Saya tahu ada fungsi di Windows untuk itu, tapi saya ingin sesuatu yang lintas platform.

Evan Fosmark
sumber
1
Pada windows saya mengalami masalah yang sama seperti pada pertanyaan ini . Solusinya adalah dengan mengganti msvcrt.getchdengan msvcrt.getwch, seperti yang disarankan di sana.
A. Roy
Solusi adalah memasang modul getch "pip install getch". Untuk Python2 gunakan perintah "pip2 instal files.pythonhosted.org/packages/56/f7/… ". Solusi ini juga berfungsi di Termux (Android).
Petr Mach

Jawaban:

190

Berikut tautan ke situs yang mengatakan bagaimana Anda bisa membaca satu karakter di Windows, Linux dan 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()
tehvan
sumber
18
kode tampaknya cukup pendek sehingga Anda bisa memasukkannya, tetapi +1 untuk menemukan jawaban (lintas platform) yang baik dengan sangat cepat.
John Mulder
4
Apakah itu menangani huruf non-latin (misalnya, cyrillic) dengan baik? Saya memiliki masalah dengan itu dan tidak bisa mencari tahu, apakah itu kesalahan saya, atau tidak.
Phlya
7
Saya tidak suka bagaimana ImportErrorpengecualian digunakan seperti semacam if-statement; mengapa tidak memanggil platform.system () untuk memeriksa OS?
Seismoid
10
@Seismoid: Meminta pengampunan umumnya dianggap lebih baik, lihat stackoverflow.com/questions/12265451/…
dirkjot
4
Tidak berfungsi di OS X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25, 'ioctl untuk perangkat yang tidak sesuai')"
Nama Tampilan
80
sys.stdin.read(1)

pada dasarnya akan membaca 1 byte dari STDIN.

Jika Anda harus menggunakan metode yang tidak menunggu, \nAnda dapat menggunakan kode ini seperti yang disarankan dalam jawaban sebelumnya:

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

( diambil dari http://code.activestate.com/recipes/134892/ )

Yuval Adam
sumber
34
Saya merasa aneh bahwa sys.stdin.read (1) menunggu \ n, lol. Terima kasih atas kirimannya.
Evan Fosmark
3
Satu karakter atau satu byte? Itu tidak sama.
chryss
4
@ Evan, itu karena python dalam modus buffered baris secara default
John La Rooy
3
@EvanFosmark: belum tentu sys.stdin.read (1) menunggu \ n, program terminal memutuskan kapan untuk mengirim karakter lain ke program Anda tidak menuliskannya sampai ia melihat '\ n' - bagaimana lagi Anda dapat menekan backspace dan memperbaiki apa yang Anda ketikkan? (jawaban seriusnya adalah - ajarkan program python untuk mengimplementasikan kontrol garis, simpan buffer, proses backspaces, tapi itu dunia yang berbeda yang mungkin tidak ingin Anda terima ketika hanya "membaca karakter", dan bisa membuat garis Anda penanganan berbeda dari semua program lain di sistem Anda.)
Tony Delroy
2
@Seismoid EAFP
vaultah
70

Resep ActiveState yang dikutip verbatim dalam dua jawaban direkayasa berlebihan. Ini bisa diringkas menjadi ini:

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()
Louis
sumber
Bagus. Tetapi ini juga akan membaca karakter pertama dari KeyboardInterrupt (Ctrl + C), dan kode memiliki kemungkinan untuk keluar 0.
user3342816
51

Juga patut dicoba adalah pustaka readchar , yang sebagian didasarkan pada resep ActiveState yang disebutkan dalam jawaban lain.

Instalasi:

pip install readchar

Pemakaian:

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

Diuji pada Windows dan Linux dengan Python 2.7.

Pada Windows, hanya kunci yang memetakan ke huruf atau kode kontrol ASCII yang didukung ( Backspace, Enter, Esc, Tab, Ctrl+ surat ). Pada GNU / Linux (tergantung pada terminal yang tepat, mungkin?) Anda juga mendapatkan Insert, Delete, Pg Up, Pg Dn, Home, Enddan kunci ... tapi kemudian, ada isu-isu yang memisahkan ini tombol khusus dariF nEsc .

Peringatan: Seperti dengan sebagian besar (semua?) Jawaban di sini, kunci sinyal seperti Ctrl+ C, Ctrl+ Ddan Ctrl+ Zditangkap dan dikembalikan ( masing - masing '\x03', '\x04'dan , '\x1a'masing - masing); program Anda bisa sulit dibatalkan.

Søren Løvborg
sumber
3
Bekerja dengan Python 3 di Linux juga. Jauh lebih baik daripada getch, karena readchar memungkinkan pencetakan untuk stdout sambil menunggu kunci (melalui utas atau asyncio).
wrobell
Diuji pada Win10 + Python 3.5 : ERROR: root: 'in <string>' memerlukan string sebagai operan kiri, bukan byte Traceback (panggilan terakhir terakhir): File ".. \ main.py", baris 184, dalam hasil wrapper = func (* args, ** kwargs) File "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", baris 41, dalam cetakan readch_eg (readchar.readchar ()) File "C: \ Users \ ipcjs \ AppData \ Local \ Program \ Python \ Python35 \ lib \ situs-paket \ readchar \ readchar_windows.py ", baris 14, di readchar sementara ch di '\ x00 \ xe0': TypeError: 'in <string>' memerlukan string sebagai operan kiri , bukan byte
ipcjs
@ipcjs tolong laporkan bug itu ke pengelola
Melih Yıldız '
1
ini jawaban terbaik menambahkan dependensi ke perpustakaan VS C ++ hanya untuk fungsi ini gila.
FistOfFury
18

Metode alternatif:

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

Dari posting blog ini .

Tyler
sumber
Tampaknya tidak berfungsi untuk saya - mengembalikan string kosong segera setelah menelepon. Di Linux dengan Python 3.6.
Marein
1
@Marein Jika Anda ingin memblokir (tunggu input), hapus | os.O_NONBLOCK. Kalau tidak, Anda bisa meletakkannya dalam satu lingkaran (ide yang bagus untuk tidur sebentar dalam lingkaran agar tidak berputar).
Chris Gregg
Dalam Python, itu lebih baik menggunakan while Truekemudian while 1.
Anonim
10

Kode ini, berdasarkan di sini , akan menaikkan KeyboardInterrupt dan EOFError dengan benar jika Ctrl+ Catau Ctrl+ Dditekan.

Harus bekerja di Windows dan Linux. Versi OS X tersedia dari sumber aslinya.

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

Jawaban peringkat teratas (saat ini) (dengan kode ActiveState) terlalu rumit. Saya tidak melihat alasan untuk menggunakan kelas ketika fungsi belaka sudah cukup. Di bawah ini adalah dua implementasi yang mencapai hal yang sama tetapi dengan kode yang lebih mudah dibaca.

Kedua implementasi ini:

  1. berfungsi dengan baik di Python 2 atau Python 3
  2. bekerja di Windows, OSX, dan Linux
  3. baca hanya satu byte (yaitu, mereka tidak menunggu baris baru)
  4. tidak bergantung pada perpustakaan eksternal
  5. mandiri (tidak ada kode di luar definisi fungsi)

Versi 1: mudah dibaca dan sederhana

def getChar():
    try:
        # for Windows-based systems
        import msvcrt # If successful, we are on Windows
        return msvcrt.getch()

    except ImportError:
        # for POSIX-based systems (with termios & tty support)
        import tty, sys, termios  # raises ImportError if unsupported

        fd = sys.stdin.fileno()
        oldSettings = termios.tcgetattr(fd)

        try:
            tty.setcbreak(fd)
            answer = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)

        return answer

Versi 2: hindari impor berulang dan penanganan pengecualian:

[EDIT] Saya melewatkan satu keuntungan dari kode ActiveState. Jika Anda berencana membaca karakter beberapa kali, kode itu menghindari biaya (diabaikan) pengulangan impor Windows dan penanganan pengecualian ImportError pada sistem mirip Unix. Meskipun Anda mungkin harus lebih peduli tentang keterbacaan kode daripada optimasi yang dapat diabaikan, berikut adalah alternatif (mirip dengan jawaban Louis, tetapi getChar () mandiri) yang berfungsi sama dengan kode ActiveState dan lebih mudah dibaca:

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

Kode contoh yang menjalankan salah satu dari versi getChar () di atas:

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))
Matthew Strax-Haber
sumber
2
Saya mengalami masalah dengan tty.setraw () saat mencetak pesan sambil secara bersamaan menunggu kunci (multi-threaded). Singkat cerita, saya menemukan bahwa menggunakan tty.setcbreak () memungkinkan Anda mendapatkan satu karakter tanpa melanggar semua hal normal lainnya. Ceritanya panjang dalam jawaban
TheDavidFactor
4

Ini mungkin kasus penggunaan untuk manajer konteks. Mengesampingkan tunjangan untuk OS Windows, berikut saran saya:

#!/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()
Alex Kleider
sumber
Anda juga bisa kembali self dalam __enter__dan memiliki readmetode yang kembali sys.stdin.read(1), maka Anda bisa membaca beberapa karakter dalam satu konteks.
L3viathan
4

Coba gunakan ini: http://home.wlu.edu/~levys/software/kbhit.py Ini bukan pemblokiran (artinya Anda dapat memiliki loop sementara dan mendeteksi penekanan tombol tanpa menghentikannya) dan lintas-platform.

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

Contoh untuk menggunakan ini:

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

Atau Anda bisa menggunakan modul getch dari PyPi . Tapi ini akan memblokir loop sementara

jdev6
sumber
3

Ini NON-BLOCKING, membaca kunci dan menyimpannya di 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

di program Anda

keypress = Keypress()

while something:
   do something
   if keypress == 'c':
        break
   elif keypress == 'i': 
       print('info')
   else:
       print("i dont understand %s" % keypress)
Davoud Taghawi-Nejad
sumber
1
@ Tumormoner: Kode ini memiliki sejumlah masalah - jadi tidak , itu tidak akan berfungsi untuk aplikasi baris perintah.
martineau
Ini berjalan untuk aplikasi baris perintah, mengingat bahwa windows manager sedang berjalan.
Davoud Taghawi-Nejad
Tidak, itu tidak berjalan di OS tanpa kepala. Tapi itu berjalan di jendela baris perintah.
Davoud Taghawi-Nejad
3

Jawaban di sini informatif, namun saya juga menginginkan cara untuk mendapatkan penekanan tombol secara tidak sinkron dan memadamkan penekanan tombol dalam acara terpisah, semuanya dengan cara yang aman, lintas platform. PyGame juga terlalu kembung untukku. Jadi saya membuat yang berikut ini (dengan Python 2.7 tapi saya curiga itu mudah dibawa-bawa), yang saya pikir saya akan bagikan di sini kalau-kalau itu berguna untuk orang lain. Saya menyimpan ini dalam file bernama 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()

Idenya adalah Anda bisa memanggil keyPress.getKey(), yang akan membaca kunci dari keyboard, lalu mengembalikannya.

Jika Anda menginginkan sesuatu lebih dari itu, saya membuat KeyCaptureobjek. Anda dapat membuatnya melalui sesuatu sepertikeys = keyPress.KeyCapture() .

Lalu ada tiga hal yang dapat Anda lakukan:

addEvent(functionName)menerima fungsi apa pun yang menggunakan satu parameter. Kemudian setiap kali tombol ditekan, fungsi ini akan dipanggil dengan string tombol itu sebagai inputnya. Ini dijalankan di utas terpisah, sehingga Anda dapat memblokir semua yang Anda inginkan di dalamnya dan itu tidak akan mengacaukan fungsi dari KeyCapturer atau menunda acara lainnya.

get()mengembalikan kunci dengan cara memblokir yang sama seperti sebelumnya. Sekarang diperlukan di sini karena kunci diambil melalui KeyCaptureobjek sekarang, sehingga keyPress.getKey()akan bertentangan dengan perilaku itu dan keduanya akan kehilangan beberapa kunci karena hanya satu kunci yang dapat diambil pada suatu waktu. Juga, katakan pengguna menekan 'a', lalu 'b', Anda menelepon get(), pengguna menekan 'c'. Itu get()panggilan akan segera kembali 'a', maka jika Anda menyebutnya lagi itu akan kembali 'b', kemudian 'c'. Jika Anda memanggilnya lagi itu akan memblokir sampai tombol lain ditekan. Ini memastikan bahwa Anda tidak melewatkan kunci apa pun, dengan cara memblokir jika diinginkan. Jadi dengan cara ini sedikit berbeda darikeyPress.getKey() dari sebelumnya

Jika Anda ingin perilaku getKey()kembali, get(lossy=True)seperti get(), kecuali bahwa itu hanya mengembalikan tombol yang ditekan setelah panggilan ke get(). Jadi pada contoh di atas,get() akan memblokir sampai pengguna menekan 'c', dan kemudian jika Anda memanggilnya lagi akan memblokir sampai tombol lain ditekan.

getAsync()sedikit berbeda. Ini dirancang untuk sesuatu yang melakukan banyak pemrosesan, kemudian sesekali kembali dan memeriksa tombol mana yang ditekan. Dengan demikian getAsync()mengembalikan daftar semua tombol yang ditekan sejak panggilan terakhir ke getAsync(), dalam urutan dari tombol terlama ditekan ke tombol paling baru ditekan. Itu juga tidak memblokir, yang berarti bahwa jika tidak ada tombol yang ditekan sejak panggilan terakhir ke getAsync(), sebuah kosong [] akan dikembalikan.

Untuk benar-benar mulai mengambil kunci, Anda perlu menelepon keys.startCapture()dengan keysobjek Anda yang dibuat di atas.startCaptureadalah non-blocking, dan hanya memulai satu utas yang hanya merekam penekanan tombol, dan utas lainnya untuk memproses penekanan tombol tersebut. Ada dua utas untuk memastikan bahwa utas yang merekam penekanan tombol tidak melewatkan kunci apa pun.

Jika Anda ingin berhenti mengambil kunci, Anda dapat memanggil keys.stopCapture()dan itu akan berhenti mengambil kunci. Namun, karena menangkap kunci adalah operasi pemblokiran, kunci penangkap utas mungkin menangkap satu kunci lagi setelah memanggil stopCapture().

Untuk mencegah hal ini, Anda dapat memasukkan parameter opsional ke dalam startCapture(functionName, args) dalam fungsi yang hanya melakukan sesuatu seperti memeriksa apakah kunci sama dengan 'c' dan kemudian keluar. Penting bahwa fungsi ini tidak banyak sebelum, misalnya, tidur di sini akan menyebabkan kita kehilangan kunci.

Namun, jika stopCapture()dipanggil dalam fungsi ini, pengambilan kunci akan segera dihentikan, tanpa mencoba menangkap lagi, dan bahwa semua get()panggilan akan segera dikembalikan, dengan Tidak ada jika belum ada tombol yang ditekan.

Juga, karena get()dan getAsync()menyimpan semua tombol sebelumnya ditekan (sampai Anda mengambilnya), Anda dapat memanggil clearGetList()dan clearAsyncList()melupakan tombol yang sebelumnya ditekan.

Perhatikan bahwa get(), getAsync()dan acara bersifat independen, jadi jika tombol ditekan: 1. Satu panggilan ke get()yang menunggu, dengan lossy aktif, akan mengembalikan kunci itu. Panggilan tunggu lainnya (jika ada) akan terus menunggu. 2. Kunci itu akan disimpan dalam antrian kunci get, sehingga get()dengan lossy off akan mengembalikan kunci tertua yang belum dikembalikan get(). 3. Semua peristiwa akan dipecat dengan kunci itu sebagai input mereka 4. Kunci itu akan disimpan dalam daftar getAsync()kunci, di mana kepar ini dikembalikan dan diatur ke daftar kosong pada panggilan berikutnya untukgetAsync()

Jika semua ini terlalu banyak, berikut adalah contoh kasus penggunaan:

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"

Ini bekerja dengan baik untuk saya dari tes sederhana yang saya buat, tetapi saya akan dengan senang hati menerima umpan balik dari orang lain juga jika ada sesuatu yang saya lewatkan.

Saya memposting ini di sini juga.

Phylliida
sumber
3

Sebuah komentar di salah satu jawaban lain menyebutkan mode cbreak, yang penting untuk implementasi Unix karena Anda biasanya tidak ingin ^ C ( KeyboardError) dikonsumsi oleh getchar (seperti ketika Anda mengatur terminal ke mode mentah, seperti yang dilakukan oleh sebagian besar jawaban lainnya).

Detail penting lainnya adalah bahwa jika Anda ingin membaca satu karakter dan bukan satu byte , Anda harus membaca 4 byte dari aliran input, karena itulah jumlah maksimum byte yang terdiri dari satu karakter dalam UTF-8 (Python 3+ ). Membaca hanya satu byte akan menghasilkan hasil yang tidak terduga untuk karakter multi-byte seperti panah keypad.

Inilah implementasi saya yang berubah untuk 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)
Nuh
sumber
2

Coba ini dengan 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, "."
PyGuy
sumber
Itu adalah ide yang rapi, tetapi tidak berfungsi pada baris perintah: pygame.error: video system not initialized
dirkjot
2

Resep ActiveState tampaknya mengandung sedikit bug untuk sistem "posix" yang mencegah Ctrl-Cdari gangguan (saya menggunakan Mac). Jika saya memasukkan kode berikut dalam skrip saya:

while(True):
    print(getch())

Saya tidak akan pernah bisa mengakhiri skrip dengan Ctrl-C, dan saya harus membunuh terminal saya untuk melarikan diri.

Saya percaya baris berikut adalah penyebabnya, dan itu juga terlalu brutal:

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

Selain itu, paket ttytidak terlalu dibutuhkan, termiossudah cukup untuk menanganinya.

Di bawah ini adalah kode yang ditingkatkan yang berfungsi untuk saya ( Ctrl-Cakan menyela), dengan getchefungsi tambahan yang menggemakan char saat Anda mengetik:

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)

Referensi:

ibic
sumber
1

The cursespaket di python dapat digunakan untuk memasukkan "mentah" mode untuk input karakter dari terminal hanya dengan beberapa pernyataan. Penggunaan utama Curses adalah untuk mengambil alih layar untuk output, yang mungkin bukan yang Anda inginkan. Cuplikan kode ini menggunakan print()pernyataan sebagai gantinya, yang dapat digunakan, tetapi Anda harus menyadari bagaimana kutukan mengubah akhir baris yang melekat pada output.

#!/usr/bin/python3
# Demo of single char terminal input in raw mode with the curses package.
import sys, curses

def run_one_char(dummy):
    'Run until a carriage return is entered'
    char = ' '
    print('Welcome to curses', flush=True)
    while ord(char) != 13:
        char = one_char()

def one_char():
    'Read one character from the keyboard'
    print('\r? ', flush= True, end = '')

    ## A blocking single char read in raw mode. 
    char = sys.stdin.read(1)
    print('You entered %s\r' % char)
    return char

## Must init curses before calling any functions
curses.initscr()
## To make sure the terminal returns to its initial settings,
## and to set raw mode and guarantee cleanup on exit. 
curses.wrapper(run_one_char)
print('Curses be gone!')
John Mark
sumber
1

Jika saya melakukan sesuatu yang rumit saya akan menggunakan kutukan untuk membaca kunci. Tapi sering kali saya hanya ingin skrip Python 3 sederhana yang menggunakan pustaka standar dan dapat membaca tombol panah, jadi saya melakukan ini:

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
Qel
sumber
0

Solusi saya untuk python3, tidak tergantung pada paket pip apa pun.

# 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())
xro
sumber
0

Saya percaya bahwa ini adalah salah satu solusi paling elegan.

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

dan kemudian menggunakannya dalam kode:

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

Jawaban yang diterima tidak bekerja dengan baik untuk saya (saya akan memegang kunci, tidak ada yang akan terjadi, maka saya akan menekan tombol lain dan itu akan berhasil).

Setelah mempelajari tentang modul kutukan , sepertinya cara yang tepat untuk melakukannya. Dan sekarang tersedia untuk Windows melalui kursor Windows (tersedia melalui pip), sehingga Anda dapat memprogram dengan cara agnostik platform. Berikut ini contoh yang terinspirasi oleh tutorial yang bagus ini di 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))

Simpan dengan .pyekstensi, atau jalankan curses.wrapper(getkey)dalam mode interaktif.

Ben Ogorek
sumber
0

Dijawab di sini: raw_input dengan python tanpa menekan enter

Gunakan kode ini-

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

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

Meir Gabay
sumber
0

Jika Anda ingin mendaftarkan hanya satu tombol, tekan sekalipun pengguna menekannya lebih dari satu kali atau terus menekan tombol lebih lama. Untuk menghindari beberapa input yang ditekan, gunakan loop sementara dan teruskan.

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)
Vinay Verma
sumber
0

jika Anda hanya ingin memegang layar sehingga Anda dapat melihat hasilnya di terminal tulis saja

input()

di akhir kode dan itu akan menahan layar

Khan Saad
sumber
-1

Build_input bawaan akan membantu.

for i in range(3):
    print ("So much work to do!")
k = raw_input("Press any key to continue...")
print ("Ok, back to work.")
Mabooka
sumber
6
raw_input sedang menunggu kunci masuk
vac