关于输入:Python从用户读取单个字符

Python read a single character from the user

有没有从用户输入中读取单个字符的方法?例如,他们在终端按下一个键,然后返回(类似于getch())。我知道Windows中有一个函数,但是我想要一个跨平台的函数。


这里有一个链接指向一个站点,说明如何在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控制代码的键(backspaceenteresctctrl+letter)。在GNU/Linux上(可能取决于确切的终端?)您还可以获得inserteletepg uppg dnhomeendf nakbd密钥…但是,将这些特殊键与esc分离时会遇到一些问题。

警告:就像大多数(全部)一样?在这里,信号键CtrL+CbaBd、CtrL+DbaBbbd和CtrL+z被捕获并返回(分别为'\x03''\x04''\x1a';您的程序可能很难中止。


另一种方法:

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+cctrl+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代码)过于复杂。当一个函数足够时,我看不出使用类的理由。下面是两个实现,它们完成了相同的事情,但具有更可读的代码。

这两种实现:

  • 在python 2或python 3中工作得很好
  • 在Windows、OSX和Linux上工作
  • 只读取一个字节(即,它们不等待换行)
  • 不依赖任何外部库
  • 是独立的(函数定义之外没有代码)
  • 版本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()

    其思想是,您可以简单地调用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对象调用keys.startCapture()startCapture是非阻塞的,只需启动一个只记录按键的线程,以及另一个处理按键的线程。有两个线程来确保记录按键的线程不会丢失任何键。好的。

    如果要停止捕获密钥,可以调用keys.stopCapture(),它将停止捕获密钥。但是,由于捕获密钥是一个阻塞操作,线程捕获密钥可能在调用stopCapture()之后再捕获一个密钥。好的。

    为了防止出现这种情况,可以将可选参数传入函数的startCapture(functionName, args)中,该函数执行类似于检查键是否等于"c",然后退出的操作。很重要的一点是,这个功能以前很少做,例如,在这里睡觉会导致我们错过钥匙。好的。

    但是,如果在该函数中调用stopCapture(),则将立即停止密钥捕获,而不再尝试捕获,并且将立即返回所有get()调用,如果尚未按下任何键,则不返回任何调用。好的。

    另外,由于get()getAsync()存储所有以前按过的键(直到您检索它们),您可以调用clearGetList()clearAsyncList()来忘记以前按过的键。好的。

    请注意,get()getAsync()和事件是独立的,因此如果按下某个键:1。一个正在等待的、有损接通的get()呼叫将返回。那把钥匙。其他等待呼叫(如果有)将继续等待。2。该密钥将存储在GET密钥队列中,这样,在关闭lossy的情况下,get()将返回尚未由get()返回的最旧密钥。三。所有事件都将以该键作为输入进行激发4。该键将存储在getAsync()键列表中,在该列表中,将返回该lis twill,并在下次调用getAsync()时设置为空列表。好的。

    如果所有这些都太多,下面是一个示例用例:好的。

    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(KeyboardError)(与大多数其他答案一样,当您将终端设置为原始模式时会使用)。

    另一个重要的细节是,如果您希望读取一个字符而不是一个字节,那么应该从输入流中读取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"系统的小错误,它可以防止Ctrl-C中断(我使用的是mac)。如果我在脚本中放入以下代码:

    1
    2
    while(True):
        print(getch())

    我永远无法用Ctrl-C终止脚本,我必须杀死终端才能逃脱。

    我相信下面这句话是原因,而且太残忍了:

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

    除此之外,并不需要tty包,termios包就足够了。

    下面是适用于我的改进代码(Ctrl-C将中断),额外的getche函数将在键入时回送字符:

    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中的curses包只需几条语句就可以用于从终端输入字符,进入"原始"模式。Curses的主要用途是接管屏幕进行输出,这可能不是您想要的。此代码段使用print()语句,这是可用的,但您必须知道curses是如何更改附加到输出的行尾的。

    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!")