Python select() 的行为很奇怪

6 投票
2 回答
793 浏览
提问于 2025-04-16 17:41

我在理解select.select的行为时遇到了一些问题。请看下面这个Python程序:

def str_to_hex(s):
    def dig(n):
        if n > 9:
            return chr(65-10+n)
        else:
            return chr(48+n)
    r = ''
    while len(s) > 0:
        c = s[0]
        s = s[1:]
        a = ord(c) / 16
        b = ord(c) % 16
        r = r + dig(a) + dig(b)
    return r

while True:
    ans,_,_ = select.select([sys.stdin],[],[])
    print ans
    s = ans[0].read(1)
    if len(s) == 0: break
    print str_to_hex(s)

我把这个程序保存到了一个名为“test.py”的文件里。如果我这样运行它:

echo 'hello' | ./test.py

那么我就能得到预期的效果:select不会阻塞,所有的数据都会被打印出来;程序随后结束。

但是如果我在交互式环境中运行这个程序,就会出现非常不理想的情况。请看下面这个控制台会话:

$ ./test.py
hello
[<open file '<stdin>', mode 'r' at 0xb742f020>]
68

程序就卡在那儿;select.select又开始阻塞了。直到我输入更多内容或者关闭输入流,接下来的字符(以及所有后面的字符)才会被打印出来,尽管其实已经有字符在等待了!有没有人能给我解释一下这种行为?我在一个流隧道程序中也遇到了类似的问题,这让整个事情变得很糟糕。

谢谢你的阅读!

2 个回答

1

它在等你发出结束信号(如果你在交互模式下,可以按 Ctrl+D 来实现)。你可以用 sys.stdin.isatty() 来检查这个脚本是否在交互模式下运行,然后根据情况来处理,比如可以使用 raw_input。我也觉得你根本不需要用 select.select,为什么不直接用 sys.stdin.read 呢?

if sys.stdin.isatty():
    while True:
        for s in raw_input():
            print str_to_hex(s)
else:
    while True:
        for s in sys.stdin.read(1):
            print str_to_hex(s)

这样做既适合交互使用,也适合流处理。

9

sys.stdinread 方法比 select 更高级一些。当你执行 ans[0].read(1) 时,实际上 Python 会从操作系统读取更多的字节,并在内部进行缓存。select 并不知道这些额外的缓存;它只知道所有数据都已经被读取,所以会一直等待,直到遇到文件结束符(EOF)或者有新的输入到来。你可以通过运行类似 strace -e read,select python yourprogram.py 的命令来观察这种行为。

一个解决方法是把 ans[0].read(1) 替换成 os.read(ans[0].fileno(), 1)os.read 是一个更底层的接口,它和操作系统之间没有任何缓存,所以它和 select 更匹配。

另外,使用 -u 这个命令行选项来运行 python 似乎也能禁用额外的缓存。

撰写回答