Python читає один символ у користувача


260

Чи існує спосіб зчитування одного символу з вводу користувача? Наприклад, вони натискають одну клавішу на терміналі, і вона повертається (подібне getch()). Я знаю, що є функція в Windows для неї, але мені б хотілося щось, що є платформою.


1
У Windows я зіткнувся з тією ж проблемою, що і в цьому питанні . Рішення полягає в тому, щоб замінити msvcrt.getchз msvcrt.getwch, як це було запропоновано тут.
А. Рой

Рішення - встановити модуль getch "pip install getch". Для Python2 використовуйте команду "pip2 install files.pythonhosted.org/packages/56/f7/… ". Це рішення також працює в Termux (Android).
Петро Мах

Відповіді:


189

Ось посилання на сайт, в якому йдеться про те, як можна прочитати один символ у Windows, Linux та 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
код здається досить коротким, що ви можете просто включити його, але +1 для швидкої пошуку хорошої (міжплатформової) відповіді.
Джон Малдер

4
Чи добре поводиться з не латинськими (наприклад, кирилицею) літерами? У мене виникають проблеми з цим і не можу зрозуміти, чи це моя помилка чи ні.
Пля

7
Мені не подобається, як ImportErrorвиняток використовується як якийсь if-оператор; чому б не зателефонувати platform.system () для перевірки ОС?
Сейсмоїд

10
@Seismoid: Просити вибачення , як правило , вважається краще, см stackoverflow.com/questions/12265451 / ...
dirkjot

4
Не працює в ОС X: "old_settings = termios.tcgetattr (fd)" "termios.error: (25," Непридатний ioctl для пристрою ")"
Відображати ім'я

79
sys.stdin.read(1)

в основному буде читати 1 байт від STDIN.

Якщо ви повинні використовувати метод, який не чекає, \nви можете використовувати цей код, як було запропоновано в попередній відповіді:

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

( взято з http://code.activestate.com/recipes/134892/ )


33
Мені здається дивним, що sys.stdin.read (1) чекає \ n, lol. Дякую за подання, хоча.
Еван Фосмарк

3
Один символ чи один байт? Це не те саме.
chryss

4
@Evan, це тому, що python за замовчуванням перебуває у режимі буферного режиму
Джон Ла Рой

3
@EvanFosmark: sys.stdin.read (1) не обов'язково чекає \ n, це те, що термінальна програма, яка вирішує, коли надсилати інших символів до вашої програми, не запише їх, поки не побачить '\ n' - як інакше ви зможете натиснути зворотну простір і виправити те, що ви вводите? (серйозна відповідь на це - навчити програму python здійснювати управління лінією, зберігати буфер, обробляти зворотні простори, але це інший світ, який ви, можливо, не хочете купувати, коли просто "читаєте персонаж", і можете зробити ваш рядок обробка відрізняється від усіх інших програм у вашій системі.)
Тоні Делрой,

2
@Seismoid EAFP
vaultah

70

Рецепт ActiveState, що цитується дослівно, у двох відповідях переосмислений. Це можна звести до цього:

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

Приємно. Але це також буде прочитати першу таблицю KeyboardInterrupt (Ctrl + C), і код має можливість вийти з 0.
користувач3342816

51

Також варто спробувати бібліотеку читачів , яка частково базується на рецепті ActiveState, згаданому в інших відповідях.

Установка:

pip install readchar

Використання:

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

Тестується в Windows і Linux з Python 2.7.

У Windows, тільки ключі, що відображають для букв або керуючих кодів ASCII підтримуються ( Backspace, Enter, Esc, Tab, Ctrl+ лист ). На GNU / Linux ( в залежності від точного терміналу, можливо?) , Ви також можете отримати Insert, Delete, Pg Up, Pg Dn, Home, Endі ключі ... але потім, є питання , що розділяють ці спеціальні ключі від .F nEsc

Застереження: Як і більшість (?) Все відповіді тут, сигнальними клавішами , як Ctrl+ C, Ctrl+ Dі Ctrl+ Zспіймані і повернені (як '\x03', '\x04'і , '\x1a'відповідно); Ваша програма може бути важко перервати.


3
Також працює з Python 3 на Linux. Набагато краще, ніж getch, тому що readchar дозволяє друкувати stdout під час очікування ключа (через нитки або асинціо).
wrobell

Тестовано на Win10 + Python 3.5: ПОМИЛКА: root: 'in <string>' вимагає рядка як лівий операнд, а не байтів Traceback (останній виклик останній): Файл ".. \ main.py", рядок 184, в обгортці результат = func (* args, ** kwargs) Файл "C: \ GitHub \ Python-Demo \ demo \ day_hello.py", рядок 41, у printch_eg print (readchar.readchar ()) Файл "C: \ Користувачі \ ipcjs \ AppData \ Локальні \ Програми \ Python \ Python35 \ lib \ site-пакети \ readchar \ readchar_windows.py ", рядок 14, в readchar, а ch у" \ x00 \ xe0 ': TypeError:' in <string> 'вимагає рядок як лівий операнд , а не байти
ipcjs

@ipcjs повідомте про те, що помилка обслуговуючим особам
Melih Yıldız '

1
це найкраща відповідь. додавання залежності до бібліотеки VS C ++ лише для цієї функціональності є божевільним.
FistOfFury

17

Альтернативний метод:

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

З цієї публікації в блозі .


Здається, це не працює для мене - повертає порожню рядок відразу після дзвінка. На Linux з Python 3.6.
Марейн

1
@Marein Якщо ви хочете, щоб його блокували (чекайте введення), видаліть | os.O_NONBLOCK. В іншому випадку ви можете покласти його в петлю (добре б трохи поспати в петлі, щоб не крутитися).
Кріс Грегг

У Python це краще використовувати while Trueтоді while 1.
Анонім

10

Цей код, заснований тут , правильно підніме KeyboardInterrupt і EOFError, якщо натиснути Ctrl+ Cабо Ctrl+ D.

Потрібно працювати в Windows та Linux. Версія OS X доступна з початкового джерела.

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

Відповідь (на даний момент) з найбільш високим рейтингом (з кодом ActiveState) надмірно складна. Я не бачу причини використовувати класи, коли достатньо просто функції. Нижче наведено дві реалізації, які виконують те саме, але з більш читабельним кодом.

Обидві ці реалізації:

  1. добре працювати в Python 2 або Python 3
  2. робота в Windows, OSX та Linux
  3. читати лише один байт (тобто вони не чекають нового рядка)
  4. не залежать від будь-яких зовнішніх бібліотек
  5. є автономними (немає коду поза визначенням функції)

Версія 1: читабельна та проста

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

Версія 2: уникайте повторного імпорту та обробки винятків:

[EDIT] Я пропустив одну перевагу коду ActiveState. Якщо ви плануєте прочитати символи кілька разів, цей код дозволяє уникнути (незначної) вартості повторення імпорту Windows та обробки винятків ImportError у Unix-подібних системах. Хоча вам, мабуть, варто більше турбуватися про читабельність коду, ніж про мізерну оптимізацію, тут є альтернатива (вона схожа на відповідь Луїса, але getChar () є самодостатньою), яка функціонує так само, як код ActiveState і є більш читаною:

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

Приклад коду, який виконує будь-яку з версій 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
Я зіткнувся з проблемою з tty.setraw () під час друку повідомлень, одночасно чекаючи клавіші (багатопотокової). Якщо коротко розповісти, я виявив, що використання tty.setcbreak () дозволяє отримати один символ, не порушуючи всіх інших нормальних речей. Довга історія у цій відповіді
TheDavidFactor

4

Це може бути випадком використання для менеджера контексту. Залишаючи осторонь припущення для ОС Windows, ось моя пропозиція:

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

Ви також можете повернутися self в __enter__і є readметод , який повертає sys.stdin.read(1), то ви можете прочитати кілька символів в одному контексті.
L3viathan

4

Спробуйте скористатися цим: http://home.wlu.edu/~levys/software/kbhit.py Це не блокує (це означає, що ви можете мати певний цикл і виявляти натискання клавіш, не зупиняючи її) та крос-платформу.

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

Приклад для використання цього:

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

Або ви можете використовувати модуль getch від PyPi . Але це перекриє цикл while


3

Це NON-BLOCKING, зчитує ключ і зберігає його в 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

у вашій програмі

keypress = Keypress()

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

1
@ThorSummoner: Цей код має ряд проблем - так що ні , він не працює для програм командного рядка.
мартіно

Він працює для програми командного рядка, враховуючи, що працює менеджер Windows.
Davoud Taghawi-Nejad

Ні, він не працює в безголовій ОС. Але він працює у вікні командного рядка.
Davoud Taghawi-Nejad

3

Відповіді тут були інформативними, однак я також хотів, щоб асинхронно натискати клавіші та знімати натискання клавіш в окремих подіях, все це захищено між платформою, що забезпечує безпеку потоків. PyGame також був занадто роздутий для мене. Тож я зробив наступне (в Python 2.7, але я підозрюю, що це легко переноситься), яким я вважав, що поділюсь тут, якщо це буде корисним для когось іншого. Я зберігав це у файлі під назвою 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()

Ідея полягає в тому, що ви можете або просто зателефонувати keyPress.getKey(), який прочитає клавішу з клавіатури, а потім повернути її.

Якщо ви хочете чогось більшого, я зробив KeyCaptureпредмет. Ви можете створити його через щось подібне keys = keyPress.KeyCapture().

Тоді ви можете зробити три речі:

addEvent(functionName)приймає будь-яку функцію, яка бере один параметр. Потім кожного разу, коли натискається клавіша, ця функція буде викликатись рядком цього ключа під час введення. Вони запускаються в окремий потік, тому ви можете заблокувати все, що ви хочете в них, і це не зіпсує функціональність KeyCapturer і не затримає інші події.

get()повертає ключ тим же блокуючим способом, що і раніше. Зараз він потрібен, тому що ключі перехоплюються через KeyCaptureоб'єкт зараз, тому keyPress.getKey()це суперечить цій поведінці, і обидва вони пропустять деякі ключі, оскільки одночасно може бути захоплено лише один ключ. Крім того, скажіть, що користувач натискає "a", потім "b", ви викликаєте get(), користувач натискає "c". Цей get()виклик негайно поверне "a", тоді, якщо ви його знову зателефонуєте, він поверне "b", то "c". Якщо ви зателефонуєте ще раз, він заблокується, поки не буде натиснута інша клавіша. Це гарантує, що ви не пропустите жодних клавіш, за бажанням блокуючим способом. Тож таким чином це трохи інакше, ніж keyPress.getKey()раніше

Якщо ви хочете, щоб поведінка getKey()спини get(lossy=True)була подібною get(), за винятком того, що вона повертає клавіші, натиснуті після дзвінка на get(). Отже, у наведеному вище прикладі get()буде заблоковано, поки користувач не натисне "c", а потім, якщо ви зателефонуєте ще раз, він заблокує, поки не буде натиснута інша клавіша.

getAsync()трохи інакше. Він розроблений для чогось, що робить багато обробки, потім періодично повертається і перевіряє, які клавіші були натиснуті. Таким чином, getAsync()повертається список усіх клавіш, натиснутих з моменту останнього дзвінка, для того getAsync(), щоб від найдавнішої натиснутої клавіші до останньої натиснутої клавіші. Він також не блокується, тобто якщо після останнього дзвінка не було натиснуто жодної клавіші getAsync(), []повернеться порожня .

Щоб насправді почати захоплювати ключі, вам потрібно зателефонувати keys.startCapture()з вашим keysоб'єктом, зробленим вище. startCaptureне блокує і просто запускає один потік, який просто записує натискання клавіш, і інший потік для обробки цих клавіш. Є два потоки для того, щоб нитка, яка записує натискання клавіш, не пропускала жодної клавіші.

Якщо ви хочете припинити захоплення ключів, ви можете зателефонувати, keys.stopCapture()і він припинить захоплення ключів. Однак, оскільки захоплення ключа є операцією блокування, ключі для захоплення потоку можуть зафіксувати ще один ключ після виклику stopCapture().

Щоб запобігти цьому, ви можете передати необов'язкові параметри ( startCapture(functionName, args)функції) функції, яка просто робить щось на зразок перевірки, чи ключ дорівнює "c", а потім виходить. Важливо, що ця функція виконує дуже мало раніше, наприклад, сон тут призведе до того, що ми не вистачимо клавіш.

Однак, якщо stopCapture()буде викликано цю функцію, захоплення клавіш буде негайно зупинено, не намагаючись зафіксувати більше, і всі get()дзвінки будуть повернені негайно, без None, якщо ще не натиснуто жодної клавіші.

Крім того, оскільки get()і getAsync()зберігати всі попередні натиснуті клавіші (поки ви не отримаєте їх), ви можете зателефонувати clearGetList()та clearAsyncList()забути раніше натиснуті клавіші.

Слід зазначити , що get(), getAsync()і події незалежні, тому , якщо натиснута кнопка: 1. Один виклик get(), що очікує, з втратами від того , буде повертати цей ключ. Інші дзвінки, що очікують (якщо такі є) продовжують очікувати. 2. Ця клавіша буде збережена у черзі клавіш get, так що get()при втраті вимкнення повернеться найдавніша натиснута ще не повернута клавіша get(). 3. Усі події будуть передані за допомогою цієї клавіші як їх вводу. Цей ключ буде збережений у списку getAsync()клавіш, де цей лист буде повернутий та встановлений у порожній список при наступному дзвінку наgetAsync()

Якщо все це занадто багато, ось приклад використання прикладу:

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"

Для мене це добре працює з простого тесту, який я зробив, але я з радістю візьму відгуки інших людей, якщо я щось пропустив.

Я також розмістив це тут .


3

У коментарі в одній з інших відповідей згадується режим зриву, що важливо для реалізації Unix, оскільки ви, як правило, не бажаєте, щоб ^ C ( KeyboardError) використовувався getchar (як це буде, коли ви встановите термінал в сирому режимі, як це робиться більшість інших відповідей).

Ще одна важлива деталь полягає в тому, що якщо ви хочете прочитати один символ, а не один байт , вам слід прочитати 4 байти з вхідного потоку, оскільки це максимальна кількість байтів, з яких складається один символ у UTF-8 (Python 3+ ). Читання лише одного байту дасть неочікувані результати для багатобайтових символів, таких як стрілки клавіатури.

Ось моя змінена реалізація для Unix:

import contextlib
import os
import sys
import termios
import tty


_MAX_CHARACTER_BYTE_LENGTH = 4


@contextlib.contextmanager
def _tty_reset(file_descriptor):
    """
    A context manager that saves the tty flags of a file descriptor upon
    entering and restores them upon exiting.
    """
    old_settings = termios.tcgetattr(file_descriptor)
    try:
        yield
    finally:
        termios.tcsetattr(file_descriptor, termios.TCSADRAIN, old_settings)


def get_character(file=sys.stdin):
    """
    Read a single character from the given input stream (defaults to sys.stdin).
    """
    file_descriptor = file.fileno()
    with _tty_reset(file_descriptor):
        tty.setcbreak(file_descriptor)
        return os.read(file_descriptor, _MAX_CHARACTER_BYTE_LENGTH)

2

Спробуйте це з пігамою:

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

Це pygame.error: video system not initialized
ідеальна

2

Рецепт ActiveState, здається, містить невелику помилку для систем "posix", що не дозволяє Ctrl-Cперерватись (я використовую Mac). Якщо я помістив у свій сценарій такий код:

while(True):
    print(getch())

Я ніколи не зможу скасувати сценарій Ctrl-C, і мені доведеться вбити свій термінал, щоб вирватися.

Я вважаю, що наступний рядок є причиною, і це теж занадто жорстоко:

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

Крім цього, пакет ttyне дуже потрібен, termiosдостатньо, щоб впоратися з ним.

Нижче наведено вдосконалений код, який працює для мене ( Ctrl-Cперерветься), з додатковою getcheфункцією, що перегукується з таблицею під час введення:

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)

Список літератури:


1

cursesПакет в Python можна використовувати для введення «сирий» режим для введення символів з терміналу кілька заяв. Основне використання прокльонів - це взяти екран для виведення, який може бути не тим, що ви хочете. Цей фрагмент коду print()натомість використовує оператори, які можна використовувати, але ви повинні знати, як прокльони змінюють закінчення рядків, додані до виводу.

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

Якщо я роблю щось складне, я буду використовувати прокльони для читання ключів. Але я часто хочу простого сценарію Python 3, який використовує стандартну бібліотеку і може читати клавіші зі стрілками, тому я роблю це:

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

Моє рішення для python3, не залежно від будь-яких пакетів піп.

# 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

Я вважаю, що це одне найелегантніше рішення.

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

а потім використовувати його в коді:

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

0

Прийнята відповідь не дуже добре підійшла до мене (я б утримував клавішу, нічого б не сталося, тоді я натиснув би іншу клавішу, і вона спрацює).

Дізнавшись про модуль прокльонів , це справді здається правильним шляхом. Тепер він доступний для Windows через вікна-курсори (доступні через pip), тож ви можете програмувати агностично. Ось приклад, натхненний цим чудовим підручником на 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))

Збережіть його за допомогою .pyрозширення або запустіть curses.wrapper(getkey)в інтерактивному режимі.


0

Тут відповіли : raw_input в python без натискання клавіші enter

Використовуйте цей код-

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

Довідка: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_press.py


0

Якщо ви хочете зареєструвати лише одну клавішу, навіть якщо користувач натискав її більше одного разу або продовжував натискати клавішу довше. Щоб уникнути отримання декількох натиснутих входів, використовуйте цикл while і пропустіть його.

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

якщо ви просто хочете провести екран, щоб ви могли побачити результат на терміналі, просто напишіть

input()

в кінці коду, і він буде тримати екран


-1

Вбудований raw_input повинен допомогти.

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 чекає клавіші введення
vac
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.