Python 3:无缓冲流与缓冲流的区别

5

我一直在使用以下代码片段来静音(重定向从Python脚本中调用的C代码的输出):

from ctypes import CDLL, c_void_p
import os
import sys

# Code
class silence(object):
    def __init__(self, stdout=os.devnull):
        self.outfile = stdout

    def __enter__(self):
        # Flush
        sys.__stdout__.flush()

        # Save
        self.saved_stream = sys.stdout
        self.fd = sys.stdout.fileno()
        self.saved_fd = os.dup(self.fd)

        # Open the redirect
        self.new_stream = open(self.outfile, 'wb', 0)

        self.new_fd = self.new_stream.fileno()

        # Replace
        os.dup2(self.new_fd, self.fd)

    def __exit__(self, *args):
        # Flush
        self.saved_stream.flush()

        # Restore
        os.dup2(self.saved_fd, self.fd)
        sys.stdout = self.saved_stream

        # Clean up
        self.new_stream.close()
        os.close(self.saved_fd)


# Test case
libc = CDLL('libc.so.6')


# Silence!
with silence():
    libc.printf(b'Hello from C in silence\n')

这个想法是重定向与 stdout 关联的 fd 并将其替换为与一个开放的空设备关联的 fd。不幸的是,在 Python 3 下它并没有按预期工作:

$ python2.7 test.py
$ python3.3 -u test.py
$ python3.3 test.py
Hello from C in silence

在Python 2.7和3.3下,使用非缓冲输出可以正常工作。我不确定其中的根本原因是什么。即使stdout被缓冲,对sys.saved_stream.flush()的调用也应该在C级别上调用fflush(stdout)(将输出刷新到null设备)。我是否误解了Python 3 I/O模型的哪个部分?

尝试使用 with stdout_redirected():(https://dev59.com/7m445IYBdhLWcg3w1tgS#22434262) - jfs
如果stdout的写入来自C库代码(例如printf和其它函数),那么redirect_stdout是无效的。 - Freddie Witherden
非常抱歉打错字了,我的评论是关于答案中提供的stdout_redirected函数的。它无法处理当C库的stdio函数与其自己的缓冲方式混合使用时出现问题(因此遇到与上面代码片段相同的问题)。 - Freddie Witherden
是的,这很可能是因为Python 3 I/O(不像Python 2)不使用stdio作为标准流,因此调用stdout.flush()可能不足够。如果您按照@Kolmar的建议添加相应的libc.fflush(None)调用,是否有所帮助?如果您只对stdio流感兴趣,则可以使用freopen()函数重定向标准stdio流。 - jfs
我认为fflush(None)是一个合理的解决方案,如果我能够找到一种可移植的方式从ctypes获取libc的句柄。 - Freddie Witherden
显示剩余2条评论
2个回答

1
我不完全确定我是否理解了Py3的I/O模型,但是添加
    sys.stdout = os.fdopen(self.fd, 'wb', 0)

在你将self.fd分配后立即修复它,这样我就可以在Python 3.4中解决它了(在添加此语句之前,我能够在3.4中重现该问题)。


0

我也不完全确定发生了什么,但在我的系统中有两种方法可以解决这个问题:

  • __exit__中将对self.saved_stream.flush()的调用替换为libc.fflush(None)
  • 在调用silence()之前使用任何字符串调用libc.printf,例如:

    libc = CDLL('/bin/cygwin1.dll')
    libc.printf(b'')
    
此外,只有使用第二种方法,在 with silence(): 块之后,Python 的 printlibc.printf 的输出才能保持同步。

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