Kullanıcı girişinden tek bir karakter okumanın bir yolu var mı? Örneğin, terminalde bir tuşa basarlar ve iade edilirler (bir çeşit gibi getch()
). Windows'da bunun için bir işlev olduğunu biliyorum, ancak çapraz platform olan bir şey istiyorum.
Kullanıcı girişinden tek bir karakter okumanın bir yolu var mı? Örneğin, terminalde bir tuşa basarlar ve iade edilirler (bir çeşit gibi getch()
). Windows'da bunun için bir işlev olduğunu biliyorum, ancak çapraz platform olan bir şey istiyorum.
Yanıtlar:
Windows, Linux ve OSX'te tek bir karakteri nasıl okuyabileceğinizi anlatan bir siteye link: 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()
ImportError
İstisnanın bir tür if-ifadesi gibi nasıl kullanıldığını sevmiyorum; işletim sistemini kontrol etmek için neden platform.system () öğesini çağırmıyorsunuz?
sys.stdin.read(1)
temel olarak STDIN'den 1 bayt okuyacaktır.
Beklemeyen yöntemi kullanmanız gerekiyorsa \n
, önceki yanıtta önerildiği gibi bu kodu kullanabilirsiniz:
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/ adresinden alınmıştır )
İki cevapta kelimesi kelimesine verilen ActiveState tarifi gereğinden fazla tasarlanmıştır. Buna kaynatılabilir:
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()
0
.
Ayrıca denemeye değer , kısmen diğer cevaplarda belirtilen ActiveState reçetesine dayanan readchar kütüphanesidir.
Kurulum:
pip install readchar
Kullanımı:
import readchar
print("Reading a char:")
print(repr(readchar.readchar()))
print("Reading a key:")
print(repr(readchar.readkey()))
Python 2.7 ile Windows ve Linux üzerinde test edilmiştir.
Windows'ta, harfler veya ASCII kontrol kodlarına haritasına yalnızca anahtarlar desteklenir ( Backspace, Enter, Esc, Tab, Ctrl+ mektup ). GNU / Linux üzerinde (belki, tam terminale bağlı?) Ayrıca olsun Insert, Delete, Pg Up, Pg Dn, Home, Endve tuşlar ... ama sonra, bir bu özel tuşlar ayıran hususlar var .F nEsc
Uyarı: gibi burada en (? Tümü) cevapları gibi sinyal tuşlarıyla Ctrl+ C, Ctrl+ Dve Ctrl+ Zyakalanıp iade edildi (gibidir '\x03'
, '\x04'
ve '\x1a'
sırasıyla); programınızı iptal etmek zor olabilir.
Alternatif bir yöntem:
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
Gönderen bu blog yayınında .
| os.O_NONBLOCK
. Aksi takdirde, bir döngüye koyabilirsiniz (dönmenin önlenmesi için döngüde biraz uyumak iyi bir fikir).
while True
daha iyidir while 1
.
Burada temel alınan bu kod, Ctrl+ Cveya Ctrl+ tuşlarına Dbasıldığında KeyboardInterrupt ve EOFError değerlerini doğru bir şekilde yükseltir .
Windows ve Linux üzerinde çalışmalıdır. OS X sürümü orijinal kaynaktan edinilebilir.
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()
(Şu anda) üst sıradaki cevap (ActiveState koduyla) aşırı derecede karmaşıktır. Sadece bir işlev yeterli olduğunda sınıfları kullanmak için bir neden göremiyorum. Aşağıda, aynı şeyi ancak daha okunabilir kodla gerçekleştiren iki uygulama bulunmaktadır.
Bu uygulamaların her ikisi de:
Sürüm 1: okunabilir ve basit
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
Sürüm 2: Tekrarlanan içe aktarmalardan ve istisna işlemeden kaçının:
[EDIT] ActiveState kodunun bir avantajını kaçırdım. Karakterleri birden çok kez okumayı planlıyorsanız, bu kod, Windows içe aktarma işlemini ve ImportError istisna işlemeyi Unix benzeri sistemlerde yinelemenin (önemsiz) maliyetini önler. Muhtemelen kodun okunabilirliği konusunda bu ihmal edilebilir optimizasyondan daha fazla endişelenmeniz gerekse de, ActiveState koduyla aynı işlevi gören ve daha okunabilir olan bir alternatif (Louis'in cevabına benzer, ancak getChar () bağımsızdır):
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()
Yukarıdaki getChar () sürümlerinden birini kullanan örnek kod:
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))
Bu, bir içerik yöneticisi için bir kullanım durumu olabilir. Windows işletim sistemi için ödenekleri bir kenara bırakmak, benim önerim:
#!/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
içinde __enter__
ve sahip read
döner bu yöntemi sys.stdin.read(1)
, o zaman bir bağlamda birden karakterleri okuyabilir.
Bunu kullanmayı deneyin: http://home.wlu.edu/~levys/software/kbhit.py Engellemez (yani bir süre döngüsüne sahip olabilirsiniz ve durmadan bir tuşa basmayı algılayabilirsiniz) ve çapraz platform.
import os
# Windows
if os.name == 'nt':
import msvcrt
# Posix (Linux, OS X)
else:
import sys
import termios
import atexit
from select import select
class KBHit:
def __init__(self):
'''Creates a KBHit object that you can call to do various keyboard things.'''
if os.name == 'nt':
pass
else:
# Save the terminal settings
self.fd = sys.stdin.fileno()
self.new_term = termios.tcgetattr(self.fd)
self.old_term = termios.tcgetattr(self.fd)
# New terminal setting unbuffered
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
# Support normal-terminal reset at exit
atexit.register(self.set_normal_term)
def set_normal_term(self):
''' Resets to normal terminal. On Windows this is a no-op.
'''
if os.name == 'nt':
pass
else:
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
def getch(self):
''' Returns a keyboard character after kbhit() has been called.
Should not be called in the same program as getarrow().
'''
s = ''
if os.name == 'nt':
return msvcrt.getch().decode('utf-8')
else:
return sys.stdin.read(1)
def getarrow(self):
''' Returns an arrow-key code after kbhit() has been called. Codes are
0 : up
1 : right
2 : down
3 : left
Should not be called in the same program as getch().
'''
if os.name == 'nt':
msvcrt.getch() # skip 0xE0
c = msvcrt.getch()
vals = [72, 77, 80, 75]
else:
c = sys.stdin.read(3)[2]
vals = [65, 67, 66, 68]
return vals.index(ord(c.decode('utf-8')))
def kbhit(self):
''' Returns True if keyboard character was hit, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select([sys.stdin], [], [], 0)
return dr != []
Bunu kullanmak için bir örnek:
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()
Veya PyPi'den getch modülünü kullanabilirsiniz . Ancak bu while döngüsünü engeller
Bu BLOK OLMAYAN, bir anahtarı okur ve keypress.key içinde saklar.
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
programında
keypress = Keypress()
while something:
do something
if keypress == 'c':
break
elif keypress == 'i':
print('info')
else:
print("i dont understand %s" % keypress)
Cevaplar burada ben de ayrı olaylarda tuşa basma kapalı uyumsuz ve yangın tuşa basma almak için bir yol istedi ancak evreli, çapraz platform şekilde tüm, bilgilendirici. PyGame benim için de şişti. Bu yüzden aşağıdakileri yaptım (Python 2.7'de ama kolayca taşınabilir olduğundan şüpheleniyorum), başka biri için yararlı olması durumunda burada paylaşacağımı düşündüm. Bunu keyPress.py adlı bir dosyada sakladım.
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()
Fikir, basitçe arayabilir keyPress.getKey()
, klavyeden bir tuş okuyabilir ve sonra geri verebilirsiniz .
Bundan daha fazlasını istiyorsan, bir KeyCapture
nesne yaptım . Gibi bir şey ile bir tane oluşturabilirsiniz keys = keyPress.KeyCapture()
.
Sonra yapabileceğiniz üç şey var:
addEvent(functionName)
bir parametreyi alan herhangi bir işlevi alır. Daha sonra bir tuşa her basıldığında, bu işlev giriş olarak o tuşun dizesiyle çağrılır. Bunlar ayrı bir iş parçacığında çalıştırılır, böylece istediğiniz her şeyi engelleyebilirsiniz ve KeyCapturer'ın işlevselliğini bozmaz veya diğer olayları geciktirmez.
get()
bir anahtarı önceki gibi engelleme yöntemiyle döndürür. Şimdi burada gerekli çünkü anahtarlar KeyCapture
şimdi nesne üzerinden ele geçiriliyor , bu nedenle keyPress.getKey()
bu davranışla çelişecek ve her seferinde sadece bir anahtar yakalanabileceğinden her ikisi de bazı anahtarları kaçıracaktı. Ayrıca, kullanıcının 'a', ardından 'b' tuşuna bastığını, aradığınızı get()
, kullanıcının 'c' tuşuna bastığını söyleyin . Bu get()
çağrı hemen 'a' döndürür, sonra tekrar ararsanız 'b', sonra 'c' döndürür. Tekrar ararsanız, başka bir tuşa basılana kadar engeller. Bu, istenirse herhangi bir anahtarı engellememenizi sağlar. Yani bu şekilde bu biraz farklı olduğunu keyPress.getKey()
önce gelen
Eğer davranışını istiyorsanız getKey()
arka get(lossy=True)
gibidir get()
sadece döner tuşlarına basıldığında dışında sonra yapılan çağrı get()
. Yukarıdaki örnekte, get()
kullanıcı 'c' tuşuna basana kadar engeller ve daha sonra tekrar ararsanız, başka bir tuşa basılana kadar engeller.
getAsync()
biraz farklı. Çok fazla işlem yapan bir şey için tasarlanmıştır, daha sonra geri döner ve hangi tuşlara basıldığını kontrol eder. Böylece , basılan en eski getAsync()
tuştan en son basılan tuşa kadar, son çağrıdan bu yana basılan tüm tuşların bir listesini döndürür getAsync()
. Ayrıca engellemez, yani son çağrıdan bu yana hiçbir tuşa basılmazsa getAsync()
boş bir []
değer döndürülür.
Anahtarları gerçekten yakalamaya başlamak keys.startCapture()
için keys
, yukarıda yapılan nesnenizle arama yapmanız gerekir . startCapture
engellemez ve yalnızca tuş basımlarını kaydeden bir iş parçacığını ve bu tuş basımlarını işlemek için başka bir iş parçacığını başlatır. Tuşa basma işlemlerini kaydeden iş parçacığının hiçbir tuşu kaçırmamasını sağlamak için iki iş parçacığı vardır.
Anahtar yakalamayı durdurmak istiyorsanız, arayabilirsiniz keys.stopCapture()
ve anahtar yakalamayı durduracaktır. Ancak, bir anahtarın yakalanması bir engelleme işlemi olduğundan, iş parçacığı yakalama anahtarları çağrıdan sonra bir anahtar daha yakalayabilir stopCapture()
.
Bunu önlemek için, isteğe bağlı bir parametre (ler) i, startCapture(functionName, args)
bir anahtarın 'c' değerine eşit olup olmadığını ve sonra çıkıp çıkmadığını kontrol etmek gibi bir işleve geçebilirsiniz . Bu işlevin daha önce çok az iş yapması önemlidir, örneğin, burada bir uyku, anahtarları kaçırmamıza neden olacaktır.
Ancak, stopCapture()
bu işlevde çağrılırsa, tuş yakalamaları artık yakalanmaya çalışılmadan hemen durdurulacak ve get()
henüz hiçbir tuşa basılmamışsa tüm çağrılar hemen döndürülecektir.
Ayrıca beri get()
ve getAsync()
(artık bu iletileri kadar), arayabileceğiniz tüm önceki tuşlarına basıldığında saklamak clearGetList()
ve clearAsyncList()
daha önce girilen tuşları unutmak.
Not get()
, getAsync()
bir tuşa basıldığında eğer öyleyse, ve olaylar bağımsızdır: 1. Bir çağrı get()
bu konuda kayıplı ile, bekliyor, o anahtarı dönecektir. Diğer bekleyen çağrılar (varsa) beklemeye devam eder. 2. Bu anahtar get anahtarları kuyruğunda saklanır, böylece get()
kayıplı kapalıyken get()
henüz basılmamış en eski anahtar döndürülür . 3. Tüm olaylar, girişleri olarak bu tuşla tetiklenir. 4. Bu tuş, getAsync()
anahtarlar listesinde saklanır ve bu lis dimi döndürülür ve sonraki çağrıda boş listeye ayarlanır.getAsync()
Bütün bunlar çok fazla ise, işte örnek bir kullanım örneği:
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"
Yaptığım basit testten benim için iyi çalışıyor, ancak kaçırdığım bir şey varsa başkalarına da geri bildirim alacağım.
Bunu da buraya gönderdim .
Genel olarak ^ C ( KeyboardError
) 'nin getchar tarafından tüketilmesini istemediğiniz için cbreak modundan bahseden diğer cevaplardan birinde bir yorum, (terminali ham moda ayarladığınız gibi, diğer cevaplar).
Bir başka önemli ayrıntı, bir bayt değil bir karakter okumak istiyorsanız, giriş akışından 4 bayt okumalısınız, çünkü bu tek bir karakterin UTF-8'de oluşacağı maksimum bayt sayısıdır (Python 3+ ). Yalnızca tek bir baytın okunması, tuş takımı okları gibi çok baytlı karakterler için beklenmedik sonuçlar üretir.
Unix için değiştirilmiş uygulamam:
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)
Bunu pygame ile deneyin:
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
ActiveState'in tarifi, "posix" sistemleri için Ctrl-C
kesintiye uğramasını önleyen küçük bir hata içeriyor gibi görünüyor (Mac kullanıyorum). Senaryomda aşağıdaki kodu koyarsam:
while(True):
print(getch())
Senaryoyu asla sonlandıramayacağım ve Ctrl-C
kaçmak için terminalimi öldürmem gerekiyor.
Aşağıdaki çizginin sebebi olduğuna inanıyorum ve aynı zamanda çok acımasız:
tty.setraw(sys.stdin.fileno())
Bunun yanı sıra, paket tty
gerçekten gerekli değildir, bununla termios
başa çıkmak için yeterlidir.
Aşağıda, yazarken karakteri yankılayan Ctrl-C
ekstra getche
işlevle benim için çalışan ( kesilecek) geliştirilmiş kod verilmiştir :
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)
Referanslar:
curses
Python paketi sadece birkaç ifadelere terminalden karakter girişi için "ham" moduna girmek için kullanılabilir. Lanetlerin ana kullanımı çıktı için ekranı ele geçirmektir, ki bu istediğiniz şey olmayabilir. Bu kod snippet'i print()
bunun yerine kullanılabilir olan ifadeleri kullanır , ancak curses'ların çıktıya ekli satır sonlarını nasıl değiştirdiğinin farkında olmalısınız.
#!/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!')
Karmaşık bir şey yapıyorsam anahtarları okumak için lanetler kullanacağım. Ama çoğu zaman sadece standart kütüphaneyi kullanan ve ok tuşlarını okuyabilen basit bir Python 3 betiği istiyorum, bu yüzden bunu yapıyorum:
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
Python3 için çözümüm, herhangi bir pip paketine bağlı değil.
# 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())
Bunun en şık çözümlerden biri olduğuna inanıyorum.
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
ve daha sonra kodda kullanın:
if getch() == chr(ESC_ASCII_VALUE):
print("ESC!")
Kabul edilen cevap benim için o kadar iyi sonuç vermedi (bir tuşa sahip olurdum, hiçbir şey olmazdı, sonra başka bir tuşa basardım ve işe yarardı).
Lanetler modülünü öğrendikten sonra , gerçekten doğru yol gibi görünüyor. Ve şimdi Windows için imleçler aracılığıyla (pip aracılığıyla kullanılabilir) kullanılabilir, böylece platform agnostik bir şekilde programlayabilirsiniz. YouTube'daki bu güzel eğiticiden ilham alan bir örnek :
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))
Bir .py
uzantıyla kaydedin veya curses.wrapper(getkey)
etkileşimli modda çalıştırın .
Burada cevaplandı: enter tuşuna basmadan python'da raw_input
Bu kodu kullanın
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()
Referans: https://github.com/unfor19/mg-tools/blob/master/mgtools/get_key_pressed.py
Kullanıcı bir kereden fazla basılı tutsa veya tuşa daha uzun süre basılı tutsa bile, yalnızca tek bir tuşa basmak istiyorsanız. Birden fazla basılı giriş almamak için while döngüsünü kullanın ve geçirin.
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)
msvcrt.getch
ile değiştirmektirmsvcrt.getwch
.