我想用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
这可能取决于你所使用的平台,甚至可能是你正在使用的终端模拟器,我不确定它是否能解决你的问题,但......
您应该能够在不调用tty.setraw
的情况下获得逐字符输入,只需将“规范模式”设置为关闭,您可以通过屏蔽ICANON
的lflag属性中的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]
,或者至少为属性索引提供符号常量。)
同时,根据您的评论,听起来^ C仅在第一或第二秒内有效,并且它与join
s中的超时有关。这是有道理的 - 主线程只是启动两个线程,两个join(1)
调用,然后完成。所以,2。启动后的几秒钟,它完成了所有的工作 - 并离开了try:
块 - 所以KeyboardInterrupt
不再有任何方式触发loop_bool = False
并发出工作线程退出的信号。
我不确定为什么你首先会在join
s上有超时,以及当他们超时时会发生什么,但有三种可能性:
try
块内,所以^ C应该能够设置loop_bool = False
。loop_bool = False
。所以只需将except
改为finally
。daemon = True
,并且当主线程完成时它们将被杀死(不好被要求关闭)。 (请注意,它的工作方式在Windows与Unix上有点不同 - 尽管你可能并不关心这个应用程序的Windows。更重要的是,所有文档都说“整个Python程序退出时没有活着的非留下守护程序线程,“因此你不应指望任何守护程序线程能够进行任何清理,但也不应指望它们不进行任何清理。不要在守护程序线程中做任何可能留下临时文件的事情周围,关键的日志消息不成文,等)