我想用python处理控制台中的键盘事件。正在运行的脚本有一些持久的输出流,当管理员触发一个按键事件时,脚本将改变其输出内容。
我用以下代码完成了它(按'q'将触发输出更改),但有两个问题
如果它太复杂,任何其他模块都可以做我想要的?我试过诅咒模块,似乎会冻结窗口输出而无法在mutlithread中进行协调
#!/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
答案 0 :(得分:1)
这可能取决于你所使用的平台,甚至可能是你正在使用的终端模拟器,我不确定它是否会解决你的问题,但是...
你应该能够在不调用tty.setraw
的情况下获得逐字符输入,只需将“规范模式”设置为关闭,这可以通过屏蔽lflag属性中的ICANON
位来实现。 tcgetattr()
。您可能还需要设置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]
,或者至少提供join
属性索引的符号常量。)
同时,根据您的评论,听起来^ C仅在第一或第二秒内有效,并且它与join(1)
中的超时有关。这是有道理的 - 主线程只是启动两个线程,执行两次try:
调用,然后完成。所以,2.启动后的几秒钟,它已完成所有工作 - 并离开KeyboardInterrupt
块 - 因此loop_bool = False
无法触发join
并发出信号工人线程退出。
我不确定为什么你首先会在try
上有超时,以及超时时会发生什么,但有三种可能性:
你不想在^ C之前退出,并且由于任何正当理由而没有超时。所以把它们拿出来。然后主线程将永远等待其他两个线程完成,并且它仍在loop_bool = False
块内,因此^ C应该能够设置loop_bool = False
。
该应用程序应在2秒后正常退出。 (我猜你会更喜欢这对线程上的单个join-any或join-all,并且有2秒的超时,但是因为Python没有任何简单的方法,所以你顺序加入了线程。)在这种情况下,您希望在超时结束时立即设置except
。因此,只需将finally
更改为daemon = True
即可。
超时总是应该足够大(大概这只是你的真实应用的简化版本),如果你超过了超时,这是一个特例。之前的选项可能仍然有效,也可能无效。如果没有,在两个线程上设置{{1}},当主线程完成时,它们将被杀死(不好被要求关闭)。 (请注意,它的工作方式在Windows与Unix上有点不同 - 尽管你可能并不关心这个应用程序的Windows。更重要的是,所有文档都说“整个Python程序退出时没有活着的非留下守护程序线程,“所以你不应指望任何守护程序线程能够进行任何清理,但也不应指望它们而不是进行任何清理。不要做任何事情可以留下临时文件的守护程序线程,不成文的关键日志消息等。)