มีวิธีอ่านอักขระหนึ่งตัวจากอินพุตของผู้ใช้หรือไม่? ตัวอย่างเช่นพวกเขากดปุ่มเดียวที่สถานีและมันจะถูกส่งกลับ (เรียงลำดับเหมือนgetch()
) ฉันรู้ว่ามีฟังก์ชั่นใน Windows สำหรับมัน แต่ฉันต้องการบางอย่างที่เป็นแพลตฟอร์มข้าม
มีวิธีอ่านอักขระหนึ่งตัวจากอินพุตของผู้ใช้หรือไม่? ตัวอย่างเช่นพวกเขากดปุ่มเดียวที่สถานีและมันจะถูกส่งกลับ (เรียงลำดับเหมือนgetch()
) ฉันรู้ว่ามีฟังก์ชั่นใน Windows สำหรับมัน แต่ฉันต้องการบางอย่างที่เป็นแพลตฟอร์มข้าม
คำตอบ:
นี่คือลิงค์ไปยังเว็บไซต์ที่ระบุว่าคุณสามารถอ่านอักขระตัวเดียวใน 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()
ImportError
ข้อยกเว้นเช่นคำสั่ง if บางประเภท ทำไมไม่เรียก platform.system () เพื่อตรวจสอบระบบปฏิบัติการ?
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/ )
สูตร 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()
0
และรหัสมีความเป็นไปได้ของการออกกับ
สิ่งที่ควรลองคือไลบรารี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+ ตัวอักษร ) บน GNU / Linux (ขึ้นอยู่กับขั้วแน่นอนบางที?) คุณยังได้รับInsert, Delete, Pg Up, Pg Dn, Home, Endและคีย์ ... แต่แล้วก็มีปัญหาแยกปุ่มพิเศษเหล่านี้จากF nEsc
ข้อแม้: เช่นเดียวกับส่วนใหญ่ (? ทั้งหมด) คำตอบในที่นี่ปุ่มสัญญาณเช่นCtrl+ C, Ctrl+ DและCtrl+ Zจะถูกจับและกลับ (ตาม'\x03'
, '\x04'
และ'\x1a'
ตามลำดับ); โปรแกรมของคุณอาจเป็นเรื่องยากที่จะยกเลิก
วิธีทางเลือก:
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
จากโพสต์บล็อกนี้
| os.O_NONBLOCK
ลบ มิฉะนั้นคุณสามารถใส่มันไว้ในลูป (แนะนำให้หลับสักนิดหน่อยเพื่อไม่ให้เกิดการหมุน)
while True
while 1
รหัสนี้ซึ่งตั้งอยู่ที่นี่จะเพิ่ม 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()
คำตอบ (ปัจจุบัน) ติดอันดับสูงสุด (ด้วยรหัส ActiveState) นั้นซับซ้อนเกินไป ฉันไม่เห็นเหตุผลที่จะใช้คลาสเมื่อฟังก์ชันเพียงควรจะพอเพียง ด้านล่างนี้คือการใช้งานสองอย่างที่ทำสิ่งเดียวกัน แต่มีโค้ดที่อ่านง่ายขึ้น
การใช้งานทั้งสองอย่างนี้:
เวอร์ชัน 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: หลีกเลี่ยงการนำเข้าซ้ำและการจัดการข้อยกเว้น:
[แก้ไข]ฉันพลาดข้อดีอย่างหนึ่งของรหัส ActiveState หากคุณวางแผนที่จะอ่านอักขระหลายครั้งรหัสนั้นจะหลีกเลี่ยงค่าใช้จ่าย (เล็กน้อย) ของการทำซ้ำการนำเข้า Windows และการจัดการข้อยกเว้น ImportError บนระบบที่คล้าย Unix แม้ว่าคุณอาจจะกังวลเกี่ยวกับความสามารถในการอ่านโค้ดมากกว่าการปรับให้เหมาะสมเล็กน้อย แต่นี่เป็นอีกทางเลือกหนึ่ง (คล้ายกับคำตอบของ Louis แต่ 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))
นี่อาจเป็นกรณีใช้งานสำหรับตัวจัดการบริบท ออกจากกันเผื่อสำหรับ Windows OS นี่คือคำแนะนำของฉัน:
#!/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)
แล้วคุณสามารถอ่านตัวละครหลายตัวในบริบทหนึ่ง
ลองใช้สิ่งนี้: 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 แต่สิ่งนี้จะบล็อกขณะที่ลูป
นี่ไม่ใช่การปิดกั้นอ่านคีย์และเก็บไว้ใน 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
ใน programm ของคุณ
keypress = Keypress()
while something:
do something
if keypress == 'c':
break
elif keypress == 'i':
print('info')
else:
print("i dont understand %s" % keypress)
คำตอบที่นี่มีข้อมูล แต่ฉันก็ต้องการวิธีที่จะได้รับการกดปุ่มแบบอะซิงโครนัสและไฟออกจากการกดปุ่มในเหตุการณ์ที่แยกจากกันทั้งหมดนี้เป็นวิธีที่ปลอดภัยสำหรับเธรดและข้ามแพลตฟอร์ม 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()
โทรทั้งหมดจะถูกส่งกลับทันทีโดยไม่มีถ้าไม่มีการกดปุ่มใด ๆ
นอกจากนี้เนื่องจาก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"
มันใช้งานได้ดีสำหรับฉันจากการทดสอบอย่างง่าย ๆ ที่ฉันทำ แต่ฉันจะรับฟังความคิดเห็นของผู้อื่นอย่างมีความสุขหากมีสิ่งที่ฉันพลาดไป
ฉันโพสต์สิ่งนี้ที่นี่เช่นกัน
ความคิดเห็นในหนึ่งในคำตอบอื่น ๆ ที่กล่าวถึงโหมด cbreak ซึ่งเป็นสิ่งสำคัญสำหรับการใช้งาน 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)
ลองด้วย 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, "."
pygame.error: video system not initialized
สูตรของ ActiveState ดูเหมือนจะมีข้อบกพร่องเล็กน้อยสำหรับระบบ "posix" ที่ป้องกันไม่ให้Ctrl-C
ขัดจังหวะ (ฉันใช้ Mac) ถ้าฉันใส่รหัสต่อไปนี้ในสคริปต์ของฉัน:
while(True):
print(getch())
ฉันจะไม่สามารถยุติสคริปต์ด้วยCtrl-C
และฉันต้องฆ่า terminal ของฉันเพื่อหนี
ฉันเชื่อว่าบรรทัดต่อไปนี้เป็นสาเหตุและมันก็โหดร้ายเกินไป:
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)
อ้างอิง:
curses
แพคเกจในหลามสามารถนำมาใช้เพื่อเข้าสู่โหมด "ดิบ" สำหรับการป้อนข้อมูลตัวละครจากสถานีมีเพียงไม่กี่งบ การใช้งานหลักของ 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!')
ถ้าฉันทำอะไรที่ซับซ้อนฉันจะใช้คำสาปเพื่ออ่านคีย์ แต่หลายครั้งฉันแค่ต้องการสคริปต์ 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
โซลูชันของฉันสำหรับ 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())
ฉันเชื่อว่านี่เป็นทางออกที่งดงามที่สุด
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!")
คำตอบที่ยอมรับไม่ได้ผลดีสำหรับฉัน (ฉันจะถือกุญแจไม่มีอะไรจะเกิดขึ้นแล้วฉันจะกดปุ่มอื่นและมันจะทำงาน)
หลังจากเรียนรู้เกี่ยวกับโมดูลคำสาปมันดูเหมือนจะเป็นวิธีที่ถูกต้อง และตอนนี้ใช้ได้สำหรับ Windows ผ่าน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)
ในโหมดโต้ตอบ
ตอบที่นี่: 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_pressed.py
หากคุณต้องการลงทะเบียนกดปุ่มเพียงครั้งเดียวเพียงครั้งเดียวแม้ว่าผู้ใช้จะกดปุ่มมากกว่าหนึ่งครั้งหรือกดปุ่มค้างนานกว่า เพื่อหลีกเลี่ยงการรับอินพุตที่กดหลายครั้งให้ใช้ขณะลูป
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)
หากคุณเพียงแค่ต้องการที่จะถือหน้าจอเพื่อให้คุณสามารถเห็นผลใน terminal เพียงแค่เขียน
input()
ในตอนท้ายของรหัสและมันจะถือหน้าจอ
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.")
msvcrt.getch
ด้วยmsvcrt.getwch
ตามที่แนะนำไว้ที่นั่น