You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python中如何为标准输入(stdin)设置超时读取?

Python中如何为标准输入(stdin)设置超时读取?

我明白你现在遇到的糟心问题——想给标准输入加个超时限制,超时后返回None或者自定义值,但写的代码根本不生效,一直卡在输入环节,甚至输入内容后计算的耗时还超过了你设置的5秒。咱们先拆解你代码里的问题,再给你两个靠谱的解决办法。

首先得说清楚:你用tty.setraw(fd)把终端设为raw模式后,os.read(fd, 5)本身是阻塞等待输入的,它不带任何超时中断机制。你用time.time()只是事后计算时长,根本没法主动打断它的等待状态,这就是为什么脚本会一直挂着,直到你输入内容才会继续执行。

接下来给你两种实用的解决思路,都帮你处理了终端恢复的问题,不会让脚本结束后终端处于怪异状态:

方法一:用select模块实现超时(推荐)

select模块可以监听文件描述符的可读状态,还能直接设置超时时间,完美适配这个场景。而且我们会在脚本结束后强制恢复终端的正常模式。

完整代码示例:

import os
import sys
import tty
import termios
import select

def read_stdin_with_timeout(timeout):
    fd = sys.stdin.fileno()
    # 先保存终端的原始配置,后面必须恢复
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        # 用select监听stdin,超时返回空列表
        ready, _, _ = select.select([sys.stdin], [], [], timeout)
        if ready:
            # 读取输入,这里设1024字节足够日常使用,可按需调整
            return os.read(fd, 1024).decode('utf-8')
        else:
            # 超时就返回None
            return None
    finally:
        # 不管脚本执行成功还是失败,都要把终端恢复成原来的样子
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

if __name__ == "__main__":
    print("Enter some text: ", end='', flush=True)
    result = read_stdin_with_timeout(5)
    if result is not None:
        print(f"\nYou entered: {result.strip()}")
    else:
        print("\nTimeout! No input received.")

关键细节说明:

  • select.select([sys.stdin], [], [], timeout):第一个参数是要监听的可读文件描述符列表,最后一个参数是超时秒数。如果超时,ready会是空列表;如果stdin有输入可读,就会返回包含stdin的列表,这时再去读取内容就不会阻塞了。
  • termios.tcgetattrtermios.tcsetattr:这俩是一对“终端配置守卫”,专门负责保存和恢复终端的原始状态,彻底解决你之前遇到的终端异常问题。

方法二:用signal模块设置闹钟信号

另一种思路是借助signal模块给进程发“超时闹钟”,当超时触发时抛出异常,直接中断阻塞的os.read操作。

完整代码示例:

import os
import sys
import tty
import termios
import signal

# 自定义超时异常
class TimeoutError(Exception):
    pass

def timeout_handler(signum, frame):
    # 超时后抛出异常,中断阻塞的读取操作
    raise TimeoutError("Input timed out")

def read_stdin_with_timeout(timeout):
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        # 注册信号处理函数,设置超时时间
        signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(timeout)
        try:
            data = os.read(fd, 1024).decode('utf-8')
            signal.alarm(0)  # 读取成功后,取消闹钟
            return data
        except TimeoutError:
            return None
    finally:
        # 必须恢复终端配置
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

if __name__ == "__main__":
    print("Enter some text: ", end='', flush=True)
    result = read_stdin_with_timeout(5)
    if result:
        print(f"\nYou entered: {result.strip()}")
    else:
        print("\nTimeout! No input received.")

注意事项:

  • 这种方法依赖进程信号机制,在多线程环境下可能会有冲突(因为信号是发给整个进程的),如果你的脚本是单线程的,用起来完全没问题。
  • 同样别忘了恢复终端配置,这步是刚需,不然终端会一直处于raw模式,输入体验会非常怪异。

内容来源于stack exchange

火山引擎 最新活动