使用Python,如何在后台等待按键时在前台运行循环?

时间:2015-07-21 22:59:31

标签: python multithreading keyboard-events

我知道(such as here)上有类似的主题,但我的设计设计稍微复杂一些。

我正在设计一个将在SSH窗口中运行的CLI脚本。该脚本将在Ubuntu 14.10服务器上托管和执行。它旨在主动监视主机交换机上端口和客户端的当前状态。每30秒或由用户定义,它将通过SNMP获取数据,然后刷新信息并将其显示在屏幕上。当它等待下一次刷新时,会有一个计时器,指示它何时会再次查询设备以获取信息。

我希望允许用户随时按特定键来更改输出视图或编辑关键变量。 (该功能类似于Unix top。)例如,按t将要求它们在循环之间输入所需的秒数。 hmi会切换显示/隐藏某些列。这些暂停计时器也不会退出循环,因为在下次刷新时会应用更改。 r会强制立即刷新并应用更改。 qCtrl+C将退出脚本。

主要活动如下:

Query loop <-----------------------------
     |                                   |
     |                              Process Data
     |                                   ^
     |                                   |
     v                               Query Data    #SNMPBULKWALK
   Timer <-------------                  ^
     | |               |                 |          
     | |        Check time remaining     |
     | |               ^                 |
     | |_______________|                 |
     |___________________________________|

使用按键中断,它会像这样:

Query loop <----------------------                      
    |                             |        ???<---Change variables
    |                        (Continue)                  ^
    V                             |                      |
  Timer <---------          !!INTERRUPT!!---------> Identify key
    | |           |               ^
    | |  Check time remaining     |
    | |           ^               |
    | |___________|               |
    |_____________________________|

我有点难过。我开始相信我可能需要实现线程 - 我没有经验 - 因为while循环本身并不能满足我们的需求。我还不确定如何将更改注入包含变量的对象(例如,计时器,显示格式的标志),因为我们的循环将不断使用它。

2 个答案:

答案 0 :(得分:1)

没什么复杂的,也没有任何需要任何包裹。

唯一的问题是它需要在程序退出时将终端恢复正常。

即。如果程序崩溃,终端将无法恢复,用户也不会看到他正在输入的内容。

但如果用户知道他在做什么,他可以强制重启shell,一切都会恢复正常。

对于corse,你可以更简单的方式使用这段代码,并使用raw_input()来完成这些工作。

from thread import start_new_thread as thread
from time import sleep
# Get only one character from stdin without echoing it to stdout
import termios
import fcntl
import sys
import os
fd = sys.stdin.fileno()
oldterm, oldflags = None, None

def prepareterm ():
    """Turn off echoing"""
    global oldterm, oldflags
    if oldterm!=None and oldflags!=None: return
    oldterm = termios.tcgetattr(fd)
    newattr = oldterm[:] # Copy of attributes to change
    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)

def restoreterm ():
    """Restore terminal to its previous state"""
    global oldterm, oldflags
    if oldterm==None and oldflags==None: return
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
    oldterm, oldflags = None, None

def getchar():
    """Get character from stdin"""
    prepareterm()
    while 1:
        try:
            c = sys.stdin.read(1)
            break
        except IOError:
            try: sleep(0.001)
            except: restoreterm(); raise KeyboardInterrupt
    restoreterm()
    return c

def control ():
    """Waits for keypress"""
    global running, done
    while running:
        c = getchar().lower()
        print "Keypress:", c
        if c=="q": print "Quitting!"; running = 0; break
    done += 1

def work ():
    """Does the server-client part"""
    global done
    while running:
        # Do your stuff here!!!
        # Just to illustrate:
        print "I am protending to work!\nPress Q to kill me!"
        sleep(1)
    print "I am done!\nExiting . . ."
    done += 1

# Just to feel better
import atexit
atexit.register(restoreterm)

# Start the program
running = 1
done = 0
thread(work, ())
thread(control, ())
# Block the program not to close when threads detach from the main one:
while running:
    try: sleep(0.2)
    except: running = 0
# Wait for both threads to finish:
while done!=2:
    try: sleep(0.001)
    except: pass # Ignore KeyboardInterrupt
restoreterm() # Just in case!

在实践中,程序永远不能在不将终端恢复正常的情况下退出,但是会发生这种情况。

现在,您可以通过仅使用一个线程并将工作循环放入主线程来简化操作,并使用raw_input从用户获取命令。 或者甚至更好,将服务器 - 客户端代码放在后台并等待主线程中的输入。

使用线程模块而不是原始线程也可能更安全。

如果您使用asyncore模块,您将使每个客户端都自己运行,并且您的主线程将被asyncore.loop()占用。 你可以覆盖它,即。重写它以检查输入和你想做的其他事情,同时保持asyncore检查同步。 此外,重负载需要覆盖其中的一些讨厌的函数,因为它的缓冲区固定为512字节,如果我没有弄错。否则,它可能是解决您问题的好方法。

最后,为了清楚起见,没有回显用户输入的代码被采用并改编自getpass模块。 只是有点我的。

答案 1 :(得分:0)

Ctrl + C很简单:它会抛出一个你可以捕获的异常(因为发生这种情况时会向进程发送一个信号)。

为了在等待时提供交互性,您应该看一下编写异步代码。 Twisted经过时间考验且能力强,但有一点学习曲线(恕我直言)。还有asyncore可能更容易入手,但更有限,我不确定它是否处理您的用例。还有asyncio,但它只存在于Python 3.4中。