Python читает один символ от пользователя
Есть ли способ чтения одного символа из пользовательского ввода? Например, они нажимают одну клавишу в терминале, и она возвращается (вроде как getch()
). Я знаю, что в Windows есть функция для этого, но я бы хотел что-то кроссплатформенное.
27 ответов
Вот ссылка на сайт, на котором написано, как можно прочитать один символ в 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()
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()
Также стоит попробовать библиотеку readchar, которая частично основана на рецепте 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+letter). В GNU/Linux (возможно, в зависимости от конкретного терминала) вы также получаете ключи Insert, Delete, Pg Up, Pg Dn, Home, End и F n... но возникают проблемы, отделяющие эти специальные ключи от Esc.
Предостережение: Как и в случае большинства (всех?) Ответов, сигнальные клавиши, такие как Ctrl+C, Ctrl+D и Ctrl+Z, перехватываются и возвращаются (как '\x03'
, '\x04'
а также '\x1a'
соответственно); Ваша программа может быть трудно прервать.
Дословный рецепт 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()
Альтернативный метод:
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
Ответ (в настоящее время) с самым высоким рейтингом (с кодом ActiveState) слишком сложен. Я не вижу смысла использовать классы, когда достаточно простой функции. Ниже приведены две реализации, которые выполняют то же самое, но с более читаемым кодом.
Обе эти реализации:
- отлично работает в Python 2 или Python 3
- работать на Windows, OSX и Linux
- читать только один байт (т.е. они не ждут новой строки)
- не зависит от каких-либо внешних библиотек
- являются автономными (нет кода вне определения функции)
Версия 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))
Этот код, основанный здесь, будет корректно вызывать 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()
Я думаю, что на этом этапе это становится очень неуклюжим, а отладка на разных платформах - большой беспорядок.
Вам было бы лучше использовать что-то вроде pyglet, pygame, cocos2d - если вы делаете что-то более сложное, чем это, и вам понадобятся визуальные эффекты или проклятия, если вы собираетесь работать с терминалом.
Проклятия стандартны: http://docs.python.org/library/curses.html
Вы можете использовать щелчок . Он хорошо протестирован и работает в Linux, Mac и Windows.
import click
print('Continue? [yn] ')
c = click.getchar() # Gets a single character
if c == 'y':
print('We will go on')
elif c == 'n':
print('Abort!')
else:
print('Invalid input :(')
Ответы здесь были информативными, однако я также хотел получить способ асинхронного нажатия клавиш и срабатывания нажатий клавиш в отдельных событиях, все в поточно-ориентированном, кроссплатформенном виде. 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://stackru.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()
будет конфликтовать с таким поведением, и оба они будут пропускать некоторые ключи, так как только один ключ может быть захвачен за один раз. Также, скажем, пользователь нажимает "а", затем "б", вы звоните get()
Пользователь нажимает "с". Тот get()
Вызов call немедленно вернет "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()
еще.
3. Все события будут запускаться с этой клавишей в качестве входных данных. 4. Эта клавиша будет сохранена в списке 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"
Это работает хорошо для меня из простого теста, который я сделал, но я с радостью приму и другие отзывы, если я что-то пропустил.
Я также разместил это здесь.
Попробуйте использовать это: http://home.wlu.edu/~levys/software/kbhit.py Это неблокирует (это означает, что вы можете иметь цикл while и обнаружить нажатие клавиши, не останавливая его) и кроссплатформенный.
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. Но это заблокирует цикл
Рецепт 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://stackru.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)
Рекомендации:
TL;DR: вот ваша кроссплатформенная копия-паста максимальной плотности без зависимостей
Я знаю, что искал это ☝️. Вы пришли сюда из Google и хотите что-то, что будет работать без pip install this-and-this? Я совершенно уверен, что это решение будет работать еще долгое время.
Пример использования
>>> getch_but_it_actually_works() # just normal key like a
'a'
>>> getch_but_it_actually_works() # a but its shift or capslock
'A'
>>> getch_but_it_actually_works() # just bare enter
'\r'
>>> getch_but_it_actually_works() # literal ESC key
'\x1b'
>>> getch_but_it_actually_works() # one of the arrow keys on linux
'\x1b[A'
>>> getch_but_it_actually_works() # one of the arrow keys on windows
'àK'
>>> getch_but_it_actually_works() # some really obscure key-combo. still works.
'\x1b[19;6~'
Кроссплатформенное решение, без внешних зависимостей
Прокрутите для более подробного ответа в конце с разумным отступом и комментариями. Это предварительный просмотр с максимальной плотностью для удобного копирования и вставки. Просто вызовите getch_but_it_actually_works()
import os
def _read_one_wide_char_win(): return msvcrt.getwch()
def _char_can_be_escape_win(char): return True if char in ("\x00", "à") else False
def _dump_keyboard_buff_win():
try: msvcrt.ungetwch("a")
except OSError: return msvcrt.getwch()
else: _ = msvcrt.getwch(); return ""
def _read_one_wide_char_nix():
old_settings = termios.tcgetattr(sys.stdin.fileno()); tty.setraw(sys.stdin.fileno())
wchar = sys.stdin.read(1)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings); return wchar
def _char_can_be_escape_nix(char): return True if char == "\x1b" else False
def _dump_keyboard_buff_nix():
old_settings = termios.tcgetattr(sys.stdin.fileno())
tty.setraw(sys.stdin.fileno()); os.set_blocking(sys.stdin.fileno(), False)
buffer_dump = ""
while char := sys.stdin.read(1): buffer_dump += char
os.set_blocking(sys.stdin.fileno(), True); termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
if buffer_dump: return buffer_dump
else: return ""
if os.name == "nt":
import msvcrt
read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_win, _char_can_be_escape_win, _dump_keyboard_buff_win
if os.name == "posix":
import termios, tty, sys
read_one_wdchar, char_can_escape, dump_key_buffer = _read_one_wide_char_nix, _char_can_be_escape_nix, _dump_keyboard_buff_nix
def getch_but_it_actually_works():
wchar = read_one_wdchar()
if char_can_escape(wchar): dump = dump_key_buffer(); return wchar + dump
else: return wchar
Длинный ответ, код с комментариями и разумными отступами
Вот длинный ответ со всеми комментариями. До сих пор нет зависимостей.
Скорее всего, это будет работать долгое время как на Linux, так и на Windows. Никаких внешних зависимостей, только встроенные.
Он также будет иметь дело с крайними случаями , такими как нажатие клавиш со стрелками или что-то неясное, например, <ctrl + shift + f12>, которое создаст длинную escape-последовательность ANSI в Linux и что-то еще в Windows. Он будет захватывать такие вещи, как <ctrl+x> или <ctrl+z> или вкладку или F1-12 как единый ввод
Я возвращался к этому посту буквально десятки раз за эти годы, так что теперь пришло время вернуть мне два цента и проценты. Ниже приведен полностью прокомментированный код.
Пример несколько длинный, но большую его часть можно пропустить. Соответствующий бит находится в самом конце , вы можете просто скопировать и вставить все это.
import os
def _read_one_wide_char_win():
"""Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
return msvcrt.getwch()
def _char_can_be_escape_win(char):
"""Return true if char could start a multipart key code (e.g.: arrows)"""
return True if char in ("\x00", "à") else False # \x00 is null character
def _dump_keyboard_buff_win():
"""If piece of multipart keycode in buffer, return it. Else return None"""
try: # msvcrt.kbhit wont work with msvcrt.getwch
msvcrt.ungetwch("a") # check buffer status by ungetching wchr
except OSError: # ungetch fails > something in buffer so >
return msvcrt.getwch() # return the buffer note: win multipart keys
else: # are always 2 parts. if ungetwch does not fail
_ = msvcrt.getwch() # clean up and return empty string
return ""
def _read_one_wide_char_nix():
"""Wait keyhit return chr. Get only 1st chr if multipart key like arrow"""
old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
tty.setraw(sys.stdin.fileno()) # set raw mode to catch raw key w/o enter
wchar = sys.stdin.read(1)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
return wchar
def _char_can_be_escape_nix(char):
"""Return true if char could start a multipart key code (e.g.: arrows)"""
return True if char == "\x1b" else False # "\x1b" is literal esc-key
def _dump_keyboard_buff_nix():
"""If parts of multipart keycode in buffer, return them. Otherwise None"""
old_settings = termios.tcgetattr(sys.stdin.fileno()) # save settings
tty.setraw(sys.stdin.fileno()) # raw to read single key w/o enter
os.set_blocking(sys.stdin.fileno(), False) # dont block for empty buffer
buffer_dump = ""
while char := sys.stdin.read(1):
buffer_dump += char
os.set_blocking(sys.stdin.fileno(), True) # restore normal settings
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
if buffer_dump:
return buffer_dump
else:
return ""
if os.name == "nt":
import msvcrt
read_one_wdchar = _read_one_wide_char_win
char_can_escape = _char_can_be_escape_win
dump_key_buffer = _dump_keyboard_buff_win
if os.name == "posix":
import termios
import tty
import sys
read_one_wdchar = _read_one_wide_char_nix
char_can_escape = _char_can_be_escape_nix
dump_key_buffer = _dump_keyboard_buff_nix
def getch_but_it_actually_works():
"""Returns a printable character or a keycode corresponding to special key
like arrow or insert. Compatible with windows and linux, no external libs
except for builtins. Uses different builtins for windows and linux.
This function is more accurately called:
"get_wide_character_or_keycode_if_the_key_was_nonprintable()"
e.g.:
* returns "e" if e was pressed
* returns "E" if shift or capslock was on
* returns "x1b[19;6~'" for ctrl + shift + F8 on unix
You can use string.isprintable() if you need to sometimes print the output
and sometimes use it for menu control and such. Printing raw ansi escape
codes can cause your terminal to do things like move cursor three rows up.
Enter will return "\ r" on all platforms (without the space seen here)
as the enter key will produce carriage return, but windows and linux
interpret it differently in different contexts on higher level
"""
wchar = read_one_wdchar() # get first char from key press or key combo
if char_can_escape(wchar): # if char is escapecode, more may be waiting
dump = dump_key_buffer() # dump buffer to check if more were waiting.
return wchar + dump # return escape+buffer. buff could be just ""
else: # if buffer was empty then we return a single
return wchar # key like "e" or "\x1b" for the ESC button
Это может быть случай использования для менеджера контекста. Оставляя в стороне допуски для ОС 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()
Если я делаю что-то сложное, я использую проклятия для чтения ключей. Но часто я просто хочу простой скрипт на 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
Это НЕ БЛОКИРОВКА, читает ключ и сохраняет его в 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)
Попробуйте это с 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, "."
Комментарий в одном из других ответов упоминал режим cbreak, который важен для реализаций Unix, потому что вы обычно не хотите ^ C (KeyboardError
) для использования getchar (как это будет при установке терминала в режим raw, как это делается в большинстве других ответов).
Еще одна важная деталь: если вы хотите прочитать один символ, а не один байт, вы должны прочитать 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)
curses
Пакет в Python может быть использован для входа в "сырой" режим для ввода символов из терминала с помощью всего нескольких операторов. Основное использование Curses - захват экрана для вывода, что может быть не тем, что вы хотите. Этот фрагмент кода использует print()
операторы, которые можно использовать, но вы должны знать, как curses изменяет окончания строк, присоединенные к выводу.
#!/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!')
Я считаю, что это одно из самых элегантных решений.
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!")
кроссплатформенным решением является Самым простымsshkeyboard . Установить с помощью
pip install sshkeyboard
,
затем напишите сценарий, например:
from sshkeyboard import listen_keyboard
def press(key):
print(f"'{key}' pressed")
def release(key):
print(f"'{key}' released")
listen_keyboard(
on_press=press,
on_release=release,
)
И он напечатает:
'a' pressed
'a' released
Когда
A
нажата клавиша.
ESC
клавиша завершает прослушивание по умолчанию.
Он требует меньше кода, чем, например, curses, tkinter и getch.
Ответ здесь: raw_input в python без нажатия клавиши ввода
Используйте этот код -
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_pressed.py
Принятый ответ не сработал для меня (я держал клавишу, ничего не происходило, затем я нажимал другую клавишу, и она работала).
После изучения модуля curses это действительно кажется правильным путем. И теперь он доступен для 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)
в интерактивном режиме.
Мое решение для python3, не зависящее от каких-либо пакетов pip.
# precondition: import tty, sys
def query_yes_no(question, default=True):
"""
Ask the user a yes/no question.
Returns immediately upon reading one-char answer.
Accepts multiple language characters for yes/no.
"""
if not sys.stdin.isatty():
return default
if default:
prompt = "[Y/n]?"
other_answers = "n"
else:
prompt = "[y/N]?"
other_answers = "yjosiá"
print(question,prompt,flush= True,end=" ")
oldttysettings = tty.tcgetattr(sys.stdin.fileno())
try:
tty.setraw(sys.stdin.fileno())
return not sys.stdin.read(1).lower() in other_answers
except:
return default
finally:
tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)
sys.stdout.write("\r\n")
tty.tcdrain(sys.stdin.fileno())
Если вы хотите зарегистрировать только одно нажатие одной клавиши, даже если пользователь нажимал ее более одного раза или продолжал нажимать клавишу дольше. Чтобы избежать множественных нажатий на ввод, используйте цикл 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)
Встроенный 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.")
Если вы просто хотите удерживать экран, чтобы увидеть результат на терминале, просто напишите
input()
в конце кода, и он будет удерживать экран