我想用 python 处理控制台中的键盘事件。正在运行的脚本有一些持久的输出流,当管理员触发一个按键事件时,脚本会改变它的输出内容。
我已经用如下代码完成了(按'q'将触发输出变化),但有两个问题
如果它太复杂,任何其他模块都可以做我想做的事?我试过curses模块,它似乎会卡住窗口输出并且无法在多线程中协调
#!/usr/bin/python
import sys
import select
import tty, termios
import threading
import time
def loop():
while loop_bool:
if switch:
output = 'aaaa'
else:
output = 'bbbb'
print output
time.sleep(0.2)
def change():
global switch
global loop_bool
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
while loop_bool:
tty.setraw(fd)
i,o,e = select.select([sys.stdin],[],[],1)
if len(i)!=0:
if i[0] == sys.stdin:
input = sys.stdin.read(1)
if input =='q':
if switch:
switch = False
else:
switch = True
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
except KeyboardInterrupt:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
loop_bool = False
try:
switch = True
loop_bool = True
t1=threading.Thread(target=loop)
t2=threading.Thread(target=change)
t1.start()
t2.start()
t1.join(1)
t2.join(1)
except KeyboardInterrupt:
loop_bool = False
最佳答案
这可能取决于您使用的平台,甚至可能取决于您使用的终端仿真器,我不确定它是否会解决您的问题,但是……
您应该能够在不调用 tty.setraw
的情况下获得逐个字符的输入。 ,只需将“规范模式”设置为关闭,您就可以屏蔽 ICANON
tcgetattr()
的 lflag 属性中的位.您可能还需要设置 VMIN 或 VTIME 属性,但默认值应该已经正确。
有关详细信息,请参阅 linux man page 中的“规范和非规范模式”部分。 , OS X man page 中的“非规范模式输入处理” ,或者如果您在不同的平台上,则等效。
将其编写为上下文管理器可能比进行显式清理更干净。特别是因为您现有的代码是 setraw
每次循环,最后才恢复;理想情况下,它们应该成对配对,并使用 with
声明保证。 (另外,这样你就不需要在 except
子句和正常流程中重复自己。)所以:
@contextlib.contextmanager
def decanonize(fd):
old_settings = termios.tcgetattr(fd)
new_settings = old_settings[:]
new_settings[3] &= ~termios.ICANON
termios.tcsetattr(fd, termios.TCSAFLUSH, new_settings)
yield
termios.tcsetattr(fd, termios.TCSAFLUSH, old_settings)
现在:
def change():
global switch
global loop_bool
with decanonize(sys.stdin.fileno()):
try:
while loop_bool:
i,o,e = select.select([sys.stdin],[],[],1)
if i and i[0] == sys.stdin:
input = sys.stdin.read(1)
if input =='q':
switch = not switch
except KeyboardInterrupt:
loop_bool = False
或者您可能想要
with
阻止在较低级别(在 while
内,或至少在 try
内)。(PS,我将您的几行代码转换为等效但更简单的形式,以删除几级嵌套。)
YMMV,但这是在我的 Mac 上的测试:
Retina:test abarnert$ python termtest.py
aaaa
aaaa
aaaa
qbbbb
bbbb
bbbb
qaaaa
aaaa
aaaa
^CRetina:test abarnert$
这让我觉得您可能想要关闭输入回声(您可以通过
new_settings[3] &= ~termios.ECHO
完成),这意味着您可能想要替换 decanonize
具有更通用的功能,用于临时设置或清除任意 termios 标志。 (另外,如果 tcgetattr
返回一个 namedtuple
而不是 list
会很好,所以你可以做 new_settings.lflag
而不是 new_settings[3]
,或者至少为属性索引提供符号常量。)同时,根据您的评论,听起来 ^C 仅在前一两秒内有效,并且与
join
中的超时有关。 s。这是有道理的——主线程只是启动了两个线程,做了两个 join(1)
调用,然后结束。所以,在启动后 2.something 秒,它完成了所有的工作——并离开了 try:
阻止——所以 KeyboardInterrupt
再也没有办法了触发 loop_bool = False
并通知工作线程退出。我不知道您为什么在
join
上超时s首先,当他们超时时应该发生什么,但有三种可能性:try
里面块,所以 ^C 应该能够设置 loop_bool = False
. loop_bool = False
一旦超时结束。所以只需更改 except
到 finally
. daemon = True
在两个线程上,当主线程完成时,它们将被杀死(不是很好地要求关闭)。 (请注意,Windows 和 Unix 上的工作方式略有不同——尽管您可能不太关心此应用程序的 Windows。更重要的是,所有文档都说“当没有事件时,整个 Python 程序会退出 -守护线程被留下”,所以你不应该指望任何守护线程能够做任何清理,但也不应该指望它们不做任何清理。不要在守护线程中做任何可能留下临时文件的事情周围,未写入的重要日志消息等)关于python - 如何使用python在控制台中获取键盘事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14133952/