readchar - Read keyboard events#

Library to easily read single chars and keystrokes.

基础用法#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4Read a single keypress from the user.
 5
 6这是 readchar 的基础用法, 在一个无限循环中不断读取用户输入的字符, 然后根据用户的输入做出相应的处理.
 7在我们这个例子中, 我们只是简单的打印出用户输入的字符.
 8"""
 9
10import readchar
11
12
13def main():
14    while 1:
15        print("before")
16        key = readchar.readkey()  # this is like input() but returns the key pressed
17        print(f"pressed {key!r}")
18        print("after")
19
20
21if __name__ == "__main__":
22    main()

缓存用户的输入并只在需要的时候进行处理#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4Buffer the input keys and process then when ENTER is pressed.
 5
 6有的时候我们希望用户完成一系列输入之后再进行处理, 这时我们就需要一个 buffer 来缓存用户的输入.
 7然后判断是否对用户的输入进行处理. 在这个例子中我们使用了一个 list 来作为 buffer, 并当用户按下
 8Enter 键后才对 buffer 中的内容进行处理.
 9"""
10
11import typing as T
12
13import readchar
14
15
16def process_entered(entered: T.List[str]):
17    keys = "".join(entered)
18    print(f"processed entered keys: {keys}")
19    print("done")
20
21
22def is_need_process(entered: T.List[str], last_key: str) -> bool:
23    return last_key == readchar.key.ENTER
24
25
26def main():
27    entered = list()
28    print("pleas enter your input and press ENTER to process it.")
29    while 1:
30        key = readchar.readkey()
31        print(f"pressed: {key!r}")
32        entered.append(key)
33        if is_need_process(entered, key):
34            process_entered(entered)
35            entered.clear()
36            print("pleas enter your input and press ENTER to process it.")
37
38
39if __name__ == "__main__":
40    main()

仅当用户一段时间都没有新的输入才进行处理#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4Only process the input when the user stopped typing for a while.
 5
 6在很多桌面 App 的设计中, 通常都会有用户在输入框中不断输入内容, 然后就会有即时的反馈. 但是当用户
 7打字很快的时候, 我们并不需要每输入一个字符就立刻进行处理. 一般我们会将用户的输入缓存起来, 然后
 8当用户停止输入了一段时间后 (一般是几百毫秒), 我们再对用户的输入进行处理.
 9
10这里注意要使用 ``time.time()`` 来获取系统时间, 而不是 CPU 时间.
11"""
12
13import typing as T
14import time
15
16import readchar
17
18
19def process_entered(entered: T.List[str]):
20    keys = "".join(entered)
21    print(f"processed entered keys: {keys}")
22
23
24def main():
25    wait_time = 1.0
26    entered = list()
27    start_time: T.Optional[float] = None  # when the user started typing
28    while 1:
29        key = readchar.readkey()
30        entered.append(key)
31        if start_time is None:
32            start_time = time.time()
33            event_time = start_time
34        else:
35            event_time = time.time()
36        print(f"pressed: {key!r}, process time {event_time}")
37        if event_time - start_time > wait_time:
38            process_entered(entered)
39            start_time = None
40            entered.clear()
41
42
43if __name__ == "__main__":
44    main()

当处理用户最新的输入时自动杀死之前的处理进程#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4这是一个更高级的用法. 通常我们每按下一个键的时候就希望后台对输入的内容进行处理并反馈, 但是这个
 5处理过程可能耗时较长. 而用户如果继续输入了新的内容, 我们就应该让之前的处理过程停止, 并开始处理新的
 6输入.
 7
 8在这个例子中我们实现了一个丢筛子游戏. 每当用户按下任意字母键的时候就会随机生成一个 1-1000 之间的
 9数字. 每次丢筛子的耗时大约为 3 秒, 如果在这 3 秒内用户再次按下任意字母键, 则会重新丢筛子, 且
10之前的丢筛子的线程会被杀死. 如果最新一次的丢筛子过了 3 秒成功结束, 而用户期间没有按下任意字母键,
11则会打印出用户按下的数字以及丢出的结果.
12"""
13
14import typing as T
15import random
16import multiprocessing
17
18import readchar
19
20
21def toss(key: str) -> T.Optional[T.Tuple[str, int]]:
22    """
23    The real long-running job.
24
25    Takes around 3 - 4 seconds
26    """
27    # st = time.time()
28    print(f"toss dice using key: {key}")
29    a_list = list(range(1000000))
30    for _ in range(10):
31        random.shuffle(a_list)
32    value = random.randint(1, 1000)
33    print(f"  the key {key} gives you {value}")
34    # print(time.time() - st)
35    return key, value
36
37
38def main():
39    process: T.Optional[multiprocessing.Process] = None
40    while True:
41        key = readchar.readchar()
42        # if this is not the first entered key, we should terminate the existing process
43        if process is not None:
44            process.terminate()  # Terminate the process immediately
45            process.join()  # Wait for the old process to finish
46        # we create a new process to run the job anyway
47        process = multiprocessing.Process(
48            target=toss,
49            args=(key,),
50        )
51        process.start()
52
53
54if __name__ == "__main__":
55    main()

Key Code#

如果你查看 readchar 的源码, 你会发现有的按键的 key code 很奇怪. 例如:

CTRL_I = TAB
CTRL_J = LF
DELETE = SUPR

这是因为一些历史原因键盘的按键码就是这样的. readchar 的处理是正确的, 不要怀疑.