Python select()的行为很奇怪

6

我有些困惑于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

如果我选择“不阻止”,那么程序将输出所有数据并正常结束,这是预期的行为。

但是,如果我交互式地运行程序,则会发生非常不希望的情况。请看下面的控制台会话:

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

该程序现在停在那里;select.select现在又被阻塞了。只有在我提供更多的输入或关闭输入流之后,下一个字符(以及其余所有字符)才会被打印出来,即使已经有字符在等待!有人能解释一下这种行为吗?我在一个流隧道程序中看到了类似的情况,它正在破坏整个事情。
感谢您的阅读!

离题了,但是 def str_to_hex(s): return ''.join(('%02x' % ord(c) for c in s)) ;-) - slowdog
@slowdog:不如使用import binascii; binascii.hexlify(s),自己写十六进制转换函数太傻了,因为已经有一个非常快的函数存在了。 - Omnifarious
@Omnifarious:哦,太酷了!我仍然低估了“内置电池”的数量。 - slowdog
2个回答

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,这似乎也可以禁用额外的缓冲。

这就是针对发帖人问题的答案。 - Omnifarious
另一种处理Python缓冲问题的方法是执行无参数读取,这应该会读取当前可用的所有内容。 - Omnifarious
有几种方法可以禁用Python的缓冲,这里概述了:https://dev59.com/inVD5IYBdhLWcg3wDHDm。通常情况下不需要禁用缓冲,我不认为在这种情况下禁用缓冲是可取的(我不知道如何在简单流处理中受益)。不过回答很好。 - Zach Kelling
谢谢!这解释了很多问题。不加参数读取是不可行的;在实际应用中,我通过SSH启动一个子进程,两个脚本使用子进程的标准输入和标准输出进行通信。 - tvynr
谢谢,这个答案对我来说非常有帮助,解决了我一直感到困惑的问题!slowdog,不幸的是,“unbuffered input”不能与“paste”一起使用。@Omnifarious,“no-parameter read”并不简单。我设法解决了细节问题,并在这里发布了它们:http://stackoverflow.com/questions/27750135/need-character-by-charter-keyboard-input-that-interacts-well-with-paste-and-ansi - Nat Kuhn

1

它正在等待您发出EOF信号(当交互式使用时,可以使用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)

这使得它既适用于交互式使用,也适用于流处理。

这不是对原帖问题的答案。 - Omnifarious
使用 sys.stdin.read 对我来说似乎比使用 select.select 更可取。当然,你可以绕过 select.select,但对我来说这似乎是一种相当丑陋的方法。 - Zach Kelling
sys.stdin.read会阻塞;使用select.select的目的是能够等待两个不同的输入源之一。 - tvynr
啊,我明白了。从你的问题中,这对我来说并不明显(尽管我猜应该是因为你一开始使用了 select.select)。 - Zach Kelling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接