python - 如何使用python在控制台中获取键盘事件

标签 python

我想用 python 处理控制台中的键盘事件。正在运行的脚本有一些持久的输出流,当管理员触发一个按键事件时,脚本会改变它的输出内容。

我已经用如下代码完成了(按'q'将触发输出变化),但有两个问题

  • 我的输出中有增加的空间。调试后,我发现代码“tty.setraw(fd)”导致了该问题,但我不知道如何解决
  • ctrl+c 不能再工作(如果 # "tty.setraw(fd)", ctrl+c 会工作)

  • 如果它太复杂,任何其他模块都可以做我想做的事?我试过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首先,当他们超时时应该发生什么,但有三种可能性:
  • 您不想在 ^C 之前退出,并且没有任何充分的理由超时。所以带他们出去。然后主线程会一直等待其他两个线程完成,而且还在try里面块,所以 ^C 应该能够设置 loop_bool = False .
  • 该应用程序应该在 2 秒后正常退出。 (我猜你会更喜欢在这对线程上使用单个 join-any 或 join-all,超时时间为 2 秒,但是因为 Python 没有任何简单的方法来做到这一点,所以你按顺序加入了线程.) 在这种情况下,您要设置 loop_bool = False一旦超时结束。所以只需更改 exceptfinally .
  • 超时总是应该足够慷慨(大概这只是您的真实应用程序的简化版本),如果您超过超时,这是一个异常(exception)情况。上一个选项可能仍然有效,也可能无效。如果没有,请设置 daemon = True在两个线程上,当主线程完成时,它们将被杀死(不是很好地要求关闭)。 (请注意,Windows 和 Unix 上的工作方式略有不同——尽管您可能不太关心此应用程序的 Windows。更重要的是,所有文档都说“当没有事件时,整个 Python 程序会退出 -守护线程被留下”,所以你不应该指望任何守护线程能够做任何清理,但也不应该指望它们不做任何清理。不要在守护线程中做任何可能留下临时文件的事情周围,​​未写入的重要日志消息等)
  • 关于python - 如何使用python在控制台中获取键盘事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14133952/

    相关文章:

    python - 每回合更新网格中每个单元格的值需要花费太多时间。 ( Ant ,AIChallenge.org)

    python - 返回实际使用的硬币列表的动态找零算法

    python - Scrapy 的 FormRequest 没有给出结果

    python - 如何从字典中选择多行(executemany select)

    python - 鼠标单击并在python中拖动

    python - 无法将 Pandas Dataframe 列转换为 float

    python - "Exercise 40: Dictionaries .."in 艰难学习 Python : for dictionaries a and b, a[b[c]] 是如何工作的?

    python - 手动功能对 3 个数字进行排序,从最低到最高

    python - 奇怪的打印......硬件

    python - 替换 Python String 中 2 个数组的值