Python read a single character from the user
有没有从用户输入中读取单个字符的方法?例如,他们在终端按下一个键,然后返回(类似于
这里有一个链接指向一个站点,说明如何在Windows、Linux和OSX中读取单个字符:http://code.activestate.com/recipes/134892/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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() |
1 | sys.stdin.read(1) |
基本上从stdin读取1个字节。
如果您必须使用不等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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配方设计过度。可以归结为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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() |
另一个值得尝试的是readchar库,它部分基于其他答案中提到的activestate配方。
安装:
1 | pip install readchar |
用途:
1 2 3 4 5 | 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、t、ctrl+letter)。在GNU/Linux上(可能取决于确切的终端?)您还可以获得insert、elete、pg up、pg dn、home、end和f nakbd密钥…但是,将这些特殊键与esc分离时会遇到一些问题。
警告:就像大多数(全部)一样?在这里,信号键CtrL+CbaBd、CtrL+DbaBbbd和CtrL+z被捕获并返回(分别为
另一种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
从这个博客文章。
我认为在这一点上它变得非常笨拙,在不同的平台上调试是一个很大的混乱。
如果你在做比这更复杂的事情,并且需要视觉效果,或者如果你要使用终端的话,你最好使用类似于pyglet,pygame,cocos2d的东西。
诅咒是标准的:http://docs.python.org/library/curses.html
如果按下ctrl+c或ctrl+d,则基于此处的代码将正确引发键盘中断和eoferror。
应该在Windows和Linux上工作。可以从原始源获得OS X版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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:可读简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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代码的一个优势。如果您计划多次读取字符,那么该代码可以避免在类似Unix的系统上重复Windows导入和importError异常处理的成本(可以忽略不计)。虽然您可能更关注代码的可读性,而不是忽略不计的优化,但这里有一个替代方案(它类似于Louis的答案,但getchar()是独立的),它的功能与ActiveState代码相同,并且更具可读性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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()版本的示例代码:
1 2 3 4 5 6 7 8 9 10 11 | 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=" >") answer = getChar() print(" ") print(responseStr.format(answer)) |
这可能是上下文管理器的用例。除了Windows操作系统的允许之外,我的建议是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #!/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() |
这里的答案很有信息性,但是我也希望有一种方法可以异步地获取按键,并在单独的事件中触发按键,所有这些都是线程安全的跨平台方式。Pygame对我来说也太臃肿了。所以我做了以下内容(在python 2.7中,但我怀疑它很容易移植),我想在这里分享一下,以防它对其他人有用。我把它存储在一个名为keypress.py的文件中。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | 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() |
其思想是,您可以简单地调用
如果你想要更多的东西,我做了一个
然后你可以做三件事:好的。
如果你想让
要真正开始捕获密钥,您需要使用上面制作的
如果要停止捕获密钥,可以调用
为了防止出现这种情况,可以将可选参数传入函数的
但是,如果在该函数中调用
另外,由于
请注意,
如果所有这些都太多,下面是一个示例用例:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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实现很重要,因为您通常不希望getchar使用^c(
另一个重要的细节是,如果您希望读取一个字符而不是一个字节,那么应该从输入流中读取4个字节,因为这是一个字符在UTF-8(python 3+)中所包含的最大字节数。仅读取一个字节将对多字节字符(如键盘箭头)产生意外结果。
以下是我为Unix更改的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) |
尝试使用:http://home.wlu.edu/~levys/software/kbhit.py它是非阻塞的(这意味着你可以有一个while循环,在不停止的情况下检测到一个按键)和跨平台的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 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 != [] |
使用此方法的示例:
1 2 3 4 5 6 7 8 9 10 | 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() |
或者可以使用PYPI中的getch模块。但这会阻塞while循环
这是非阻塞的,读取一个键并将其存储在keypress.key中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
在您的程序中
1 2 3 4 5 6 7 8 9 10 | keypress = Keypress() while something: do something if keypress == 'c': break elif keypress == 'i': print('info') else: print("i dont understand %s" % keypress) |
用Pygame试试这个:
1 2 3 4 5 6 7 8 | 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,"." |
ActiveState的配方似乎包含一个"posix"系统的小错误,它可以防止
1 2 | while(True): print(getch()) |
我永远无法用
我相信下面这句话是原因,而且太残忍了:
1 | tty.setraw(sys.stdin.fileno()) |
除此之外,并不需要
下面是适用于我的改进代码(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) |
参考文献:
- https://pypi.python.org/pypi/getch网站
python中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #!/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(' ? ', flush= True, end = '') ## A blocking single char read in raw mode. char = sys.stdin.read(1) print('You entered %s ' % 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脚本,它使用标准库,可以读取箭头键,所以我这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
内置的原始输入应该有帮助。
1 2 3 4 | for i in range(3): print ("So much work to do!") k = raw_input("Press any key to continue...") print ("Ok, back to work.") |
我的python3解决方案,不依赖于任何PIP包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 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(" ") tty.tcdrain(sys.stdin.fileno()) |
我相信这是一个最优雅的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
然后在代码中使用它:
1 2 | if getch() == chr(ESC_ASCII_VALUE): print("ESC!") |